Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 83 additions & 2 deletions src/ELF/ELFVersion.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export ELFVersionData
export ELFVersionData, ELFVersionNeededData, ELFHash

# Special ELF version data structures
@io struct ELFVerDef{H <: ELFHandle}
Expand All @@ -24,14 +24,37 @@ end
vn_next::UInt32
end

@io struct ELFVernAux{H <: ELFHandle}
vna_hash::UInt32
vna_flags::UInt16
vna_other::UInt16
vna_name::UInt32
vna_next::UInt32
end

struct ELFVersionEntry{H <: ELFHandle}
ver_def::ELFVerDef{H}
names::Vector{String}
end

struct ELFVersionNeededEntry{H <: ELFHandle}
ver_need::ELFVerNeed{H}
auxes::Vector{ELFVernAux}
names::Vector{String}
end

"""
Collect all version definitions from .gnu.version_d. This section contains a
sequence of verdef structs `vd`, each of which owns exactly `vd.vd_cnt` verdaux
structs which we convert to names. The first name is generally the only
important one to the given `vd`; it is the version being defined, and
corresponds to `vd.vd_hash`. If present, a second verdaux usually notes the
parent version (e.g. `names = ["GLIBCXX_3.4.7", "GLIBCXX_3.4.6"]`)
"""
function ELFVersionData(oh::H) where {H <: ELFHandle}
s = findfirst(Sections(oh), ".gnu.version_d")
strtab = StrTab(findfirst(Sections(oh), ".dynstr"))
(isnothing(s) || isnothing(strtab)) && return ELFVersionEntry[]

# Queue oh up to the beginning of this section
seek(oh, section_offset(s))
Expand Down Expand Up @@ -62,4 +85,62 @@ function ELFVersionData(oh::H) where {H <: ELFHandle}
end

return version_defs
end
end

"""
Collect all version requirements from .gnu.version_r. This section is
structurally similar to the version definition section, but the primary
"verneed" struct corresponds to one shared library, and the auxiliary struct
corresponds to a version.
"""
function ELFVersionNeededData(oh::H) where {H <: ELFHandle}
s = findfirst(Sections(oh), ".gnu.version_r")
strtab = StrTab(findfirst(Sections(oh), ".dynstr"))
(isnothing(s) || isnothing(strtab)) && return ELFVersionNeededEntry[]

seek(oh, section_offset(s))
verneeds = ELFVersionNeededEntry[]
while true
vn_pos = position(oh)
vn = unpack(oh, ELFVerNeed{H})
auxes = ELFVernAux[]
names = String[]
aux_offset = 0
for aux_idx in 1:vn.vn_cnt
seek(oh, vn_pos + vn.vn_aux + aux_offset)
aux = unpack(oh, ELFVernAux{H})
name = strtab_lookup(strtab, aux.vna_name)
push!(auxes, aux)
push!(names, name)
aux_offset += aux.vna_next
end
push!(verneeds, ELFVersionNeededEntry(vn, auxes, names))

if vn.vn_next == 0
break
end
seek(oh, vn_pos + vn.vn_next)
end

return verneeds
end

"""
See https://en.wikipedia.org/wiki/PJW_hash_function

Hash function used to create vd_hash from vda_name, or vna_hash from vna_name.
Stops at the first null byte.
"""
function ELFHash(v::Vector{UInt8})
h = UInt32(0)
for b in v
(b == 0) && break;
h = (h << 4) + b
hi = h & 0xf0000000
if (hi != 0)
h ⊻= (hi >> 24)
end
h &= ~hi
end
return h
end
41 changes: 35 additions & 6 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -210,19 +210,48 @@ test_libfoo_and_fooifier("./win64/fooifier.exe", "./win64/libfoo.dll")

# Ensure that ELF version stuff works
@testset "ELF Version Info Parsing" begin
using ObjectFile.ELF

# Assuming the version structs in the file are correct, test that we read
# them correctly (and calculate hashes correctly).
function check_verdef(v::ELF.ELFVersionEntry)
@test v.ver_def.vd_version == 1
@test v.ver_def.vd_cnt == length(v.names)
if length(v.names) > 0
@test v.ver_def.vd_hash == ELFHash(Vector{UInt8}(v.names[1]))
end
end
function check_verneed(v::ELF.ELFVersionNeededEntry)
@test v.ver_need.vn_version == 1
@test v.ver_need.vn_cnt == length(v.auxes) == length(v.names)
for i in 1:length(v.names)
@test v.auxes[i].vna_hash == ELFHash(Vector{UInt8}(v.names[i]))
end
end

libstdcxx_path = "./linux64/libstdc++.so.6"

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

for p in ["./linux32/fooifier", "./linux32/libfoo.so",
"./linux64/fooifier", "./linux64/libfoo.so",
"./linux64/libstdc++.so.6"]
readmeta(p) do ohs
oh = only(ohs)
foreach(check_verdef, ELFVersionData(oh))
foreach(check_verneed, ELFVersionNeededData(oh))
end
end

end

# Ensure that these tricksy win32 files work
@testset "git win32 problems" begin
Expand Down
Loading