Skip to content

Commit 3ac32f3

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 2e95c7b commit 3ac32f3

3 files changed

Lines changed: 96 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: 85 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,85 @@ 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("B5cxx11", symbol_name) ||
223+
occursin("std::__cxx11", symbol_name) ||
224+
occursin("[abi:cxx11]", symbol_name)
225+
has_cxx03_marker(symbol_name::AbstractString) = startswith(symbol_name, "_ZNSs") || startswith(symbol_name, "_ZNSb")
226+
227+
function detect_cxxstring_abi(symbol_names::Vector{<:AbstractString}, platform::AbstractPlatform)
228+
# Fast paths on mangled names. These avoid invoking c++filt for large C++
229+
# libraries when the ABI evidence is already visible in the raw symbol names.
230+
if any(has_cxx11_marker, symbol_names)
231+
return "cxx11"
232+
end
233+
if any(has_cxx03_marker, symbol_names)
234+
return "cxx03"
235+
end
236+
237+
demangled_names = cppfilt(symbol_names, platform; strip_underscore=Sys.isapple(platform))
238+
if any(occursin("[abi:cxx11]", c) || occursin("std::__cxx11", c) for c in demangled_names)
239+
return "cxx11"
240+
end
241+
if any(occursin("std::string", c) || occursin("std::basic_string", c) ||
242+
occursin("std::list", c) for c in demangled_names)
243+
return "cxx03"
244+
end
245+
return nothing
246+
end
247+
248+
detect_cxxstring_abi_from_symbols(syms, platform::AbstractPlatform) = detect_cxxstring_abi(abi_symbol_names(syms), platform)
249+
250+
function detect_dynamic_cxx11_abi(oh::ELFHandle)
251+
syms = dynamic_abi_symbols(oh)
252+
syms === nothing && return nothing
253+
254+
strtab = read(StrTab(syms).section_ref)
255+
for sym in syms
256+
symbol_name = lookup_strtab(strtab, deref(sym).st_name)
257+
has_cxx11_marker(symbol_name) && return "cxx11"
258+
end
259+
return nothing
260+
end
261+
262+
function detect_cxxstring_abi_from_symbols(syms::ELFSymbols, platform::AbstractPlatform)
263+
strtab = read(StrTab(syms).section_ref)
264+
symbol_names = String[]
265+
found_cxx03 = false
266+
for sym in syms
267+
symbol_name = lookup_strtab(strtab, deref(sym).st_name)
268+
if has_cxx11_marker(symbol_name)
269+
return "cxx11"
270+
end
271+
found_cxx03 |= has_cxx03_marker(symbol_name)
272+
push!(symbol_names, symbol_name)
273+
end
274+
found_cxx03 && return "cxx03"
275+
return detect_cxxstring_abi(symbol_names, platform)
276+
end
277+
198278
"""
199279
detect_cxxstring_abi(oh::ObjectHandle, platform::AbstractPlatform)
200280
@@ -210,20 +290,12 @@ function detect_cxxstring_abi(oh::ObjectHandle, platform::AbstractPlatform)
210290
return nothing
211291
end
212292

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"
293+
if isa(oh, ELFHandle)
294+
cxx_abi = detect_dynamic_cxx11_abi(oh)
295+
cxx_abi === nothing || return cxx_abi
226296
end
297+
298+
return detect_cxxstring_abi_from_symbols(Symbols(oh), platform)
227299
catch e
228300
if isa(e, InterruptException)
229301
rethrow(e)

test/auditing.jl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@ 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__cxx11_helper", "_ZNSs4sizeEv"], platform) == "cxx03"
50+
@test Auditor.detect_cxxstring_abi(["plain_c_symbol"], platform) === nothing
51+
end
52+
4353
@testset "Auditor - ISA tests" begin
4454
@test compatible_marchs(Platform("x86_64", "linux")) == ["x86_64"]
4555
@test compatible_marchs(Platform("x86_64", "linux"; march="x86_64")) == ["x86_64"]

0 commit comments

Comments
 (0)