Skip to content

Commit c4b2dce

Browse files
committed
Speed up C++ ABI audit with ELF cxx11 precheck
Use ELF dynamic symbols as a fast positive check for libstdc++ cxx11 ABI markers, then fall back to the full existing symbol-table audit when that check is inconclusive. Recognize both the std::__cxx11 inline namespace and GCC ABI-tag manglings such as B5cxx11, so ABI-tagged cxx11 exports still win over old-ABI _ZNSs references. This removes the pathological LLVM audit cost for obvious cxx11 libraries without weakening cxx03 detection or no-evidence cases. Tests cover raw cxx11 namespace markers, GCC ABI tags mixed with old-ABI symbols, cxx03-only markers, and no-evidence cases.
1 parent 1395f3d commit c4b2dce

3 files changed

Lines changed: 94 additions & 14 deletions

File tree

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ JLD2 = "0.1.6, 0.2, 0.3, 0.4, 0.5, 0.6"
4343
JLLWrappers = "1.2.0"
4444
JSON = "0.21, 1"
4545
LoggingExtras = "0.4, 1"
46-
ObjectFile = "0.4.3"
46+
ObjectFile = "0.4.3, 0.5"
4747
Patchelf_jll = "0.14.3"
4848
PkgLicenses = "0.2"
4949
Registrator = "1.1"

src/auditor/compiler_abi.jl

Lines changed: 84 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Base.BinaryPlatforms: detect_libstdcxx_version, detect_cxxstring_abi
22
using ObjectFile
3+
using ObjectFile.ELF
34
using Binutils_jll: Binutils_jll
45

56
csl_warning(lib) = @lock AUDITOR_LOGGING_LOCK @warn(
@@ -195,6 +196,84 @@ function cppfilt(symbol_names::Vector, platform::AbstractPlatform; strip_undersc
195196
return filter!(s -> !isempty(s), split(String(take!(output)), "\n"))
196197
end
197198

199+
function dynamic_abi_symbols(oh::ELFHandle)
200+
dyn_sections = findall(Sections(oh), ".dynsym")
201+
if !isempty(dyn_sections)
202+
return Symbols(first(dyn_sections))
203+
end
204+
return nothing
205+
end
206+
207+
abi_symbol_names(syms) = symbol_name.(syms)
208+
209+
function lookup_strtab(strtab::AbstractVector{UInt8}, index::Integer)
210+
i = Int(index) + 1
211+
j = findnext(==(0x00), strtab, i)
212+
j === nothing && return String(strtab[i:end])
213+
return String(strtab[i:j-1])
214+
end
215+
216+
function abi_symbol_names(syms::ELFSymbols)
217+
strtab = read(StrTab(syms).section_ref)
218+
return [lookup_strtab(strtab, deref(sym).st_name) for sym in syms]
219+
end
220+
221+
has_cxx11_marker(symbol_name::AbstractString) = occursin("St7__cxx11", symbol_name) ||
222+
occursin("__cxx11", symbol_name) ||
223+
occursin("B5cxx11", symbol_name)
224+
has_cxx03_marker(symbol_name::AbstractString) = startswith(symbol_name, "_ZNSs") || startswith(symbol_name, "_ZNSb")
225+
226+
function detect_cxxstring_abi(symbol_names::Vector{<:AbstractString}, platform::AbstractPlatform)
227+
# Fast paths on mangled names. These avoid invoking c++filt for large C++
228+
# libraries when the ABI evidence is already visible in the raw symbol names.
229+
if any(has_cxx11_marker, symbol_names)
230+
return "cxx11"
231+
end
232+
if any(has_cxx03_marker, symbol_names)
233+
return "cxx03"
234+
end
235+
236+
demangled_names = cppfilt(symbol_names, platform; strip_underscore=Sys.isapple(platform))
237+
if any(occursin("[abi:cxx11]", c) || occursin("std::__cxx11", c) for c in demangled_names)
238+
return "cxx11"
239+
end
240+
if any(occursin("std::string", c) || occursin("std::basic_string", c) ||
241+
occursin("std::list", c) for c in demangled_names)
242+
return "cxx03"
243+
end
244+
return nothing
245+
end
246+
247+
detect_cxxstring_abi_from_symbols(syms, platform::AbstractPlatform) = detect_cxxstring_abi(abi_symbol_names(syms), platform)
248+
249+
function detect_dynamic_cxx11_abi(oh::ELFHandle)
250+
syms = dynamic_abi_symbols(oh)
251+
syms === nothing && return nothing
252+
253+
strtab = read(StrTab(syms).section_ref)
254+
for sym in syms
255+
symbol_name = lookup_strtab(strtab, deref(sym).st_name)
256+
has_cxx11_marker(symbol_name) && return "cxx11"
257+
end
258+
return nothing
259+
end
260+
261+
function detect_cxxstring_abi_from_symbols(syms::ELFSymbols, platform::AbstractPlatform)
262+
strtab = read(StrTab(syms).section_ref)
263+
symbol_names = String[]
264+
found_cxx03 = false
265+
for sym in syms
266+
symbol_name = lookup_strtab(strtab, deref(sym).st_name)
267+
if has_cxx11_marker(symbol_name)
268+
return "cxx11"
269+
end
270+
found_cxx03 |= has_cxx03_marker(symbol_name)
271+
push!(symbol_names, symbol_name)
272+
end
273+
found_cxx03 && return "cxx03"
274+
return detect_cxxstring_abi(symbol_names, platform)
275+
end
276+
198277
"""
199278
detect_cxxstring_abi(oh::ObjectHandle, platform::AbstractPlatform)
200279
@@ -210,20 +289,12 @@ function detect_cxxstring_abi(oh::ObjectHandle, platform::AbstractPlatform)
210289
return nothing
211290
end
212291

213-
# GCC on macOS prepends an underscore to symbols, strip it.
214-
symbol_names = cppfilt(symbol_name.(Symbols(oh)), platform; strip_underscore=Sys.isapple(platform))
215-
# Shove the symbol names through c++filt (since we don't want to have to
216-
# reimplement the parsing logic in Julia). If anything has `cxx11` tags,
217-
# then mark it as such.
218-
if any(occursin("[abi:cxx11]", c) || occursin("std::__cxx11", c) for c in symbol_names)
219-
return "cxx11"
220-
end
221-
# Otherwise, if we still have `std::string`'s or `std::list`'s in there, it's implicitly a
222-
# `cxx03` binary, even though we don't have a __cxx03 namespace or something. Mark it.
223-
if any(occursin("std::string", c) || occursin("std::basic_string", c) ||
224-
occursin("std::list", c) for c in symbol_names)
225-
return "cxx03"
292+
if isa(oh, ELFHandle)
293+
cxx_abi = detect_dynamic_cxx11_abi(oh)
294+
cxx_abi === nothing || return cxx_abi
226295
end
296+
297+
return detect_cxxstring_abi_from_symbols(Symbols(oh), platform)
227298
catch e
228299
if isa(e, InterruptException)
229300
rethrow(e)

test/auditing.jl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,15 @@ using ObjectFile
4040
])
4141
end
4242

43+
@testset "Auditor - cxxabi symbol markers" begin
44+
platform = Platform("x86_64", "linux")
45+
@test Auditor.detect_cxxstring_abi(["_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE6lengthEv"], platform) == "cxx11"
46+
@test Auditor.detect_cxxstring_abi(["_ZNSs4sizeEv"], platform) == "cxx03"
47+
@test Auditor.detect_cxxstring_abi(["_ZNSs4sizeEv", "_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE6lengthEv"], platform) == "cxx11"
48+
@test Auditor.detect_cxxstring_abi(["_ZNSs4_Rep9_S_createEmmRKSaIcE", "_Z3fooB5cxx11v"], platform) == "cxx11"
49+
@test Auditor.detect_cxxstring_abi(["plain_c_symbol"], platform) === nothing
50+
end
51+
4352
@testset "Auditor - ISA tests" begin
4453
@test compatible_marchs(Platform("x86_64", "linux")) == ["x86_64"]
4554
@test compatible_marchs(Platform("x86_64", "linux"; march="x86_64")) == ["x86_64"]

0 commit comments

Comments
 (0)