diff --git a/src/ELF/ELFVersion.jl b/src/ELF/ELFVersion.jl index 8c303b6..859bbf3 100644 --- a/src/ELF/ELFVersion.jl +++ b/src/ELF/ELFVersion.jl @@ -1,4 +1,4 @@ -export ELFVersionData +export ELFVersionData, ELFVersionNeededData, ELFHash # Special ELF version data structures @io struct ELFVerDef{H <: ELFHandle} @@ -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)) @@ -62,4 +85,62 @@ function ELFVersionData(oh::H) where {H <: ELFHandle} end return version_defs -end \ No newline at end of file +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 diff --git a/test/runtests.jl b/test/runtests.jl index dbde158..447bcc7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -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