os/linux/elf: fix file descriptor leak
On Linux, we occasionally see `EMFILE` ("too many open files") errors
especially when installing a large formula like `llvm`. Currently, this
can be reliably reproduced in a Homebrew/brew GitHub codespace (where
`ulimit -n` seems to be 1024 by default) with `brew install geeqie`,
with the following error message:
Error: Too many open files @ rb_sysopen - /home/linuxbrew/.linuxbrew/Cellar/llvm/20.1.8/bin/tblgen-lsp-server
The reason is that each instance of `PatchELF::Patcher` keeps the ELF
file open. We prepend the `ELFShim` module to the `Pathname` class and
cache the patcher as an instance variable, which means that the ELF file
remains open so long as the `Pathname` instance is still alive even if
we don't need to access the ELF metadata anymore. When performing
certain checks (e.g., linkage), we also store these `Pathname`
instances, so the number of open file descriptors simply keeps
increasing.
We can fix that by not caching the patcher and only use it when
necessary. We create a patcher instance whenever we need to read or
write ELF metadata, and reading of metadata is consolidated into the
existing `ELFShim::Metadata` class so that we don't repeatedly create
patcher instances.
A fix for a file descriptor leak issue in patchelf.rb has been submitted
at https://github.com/david942j/patchelf.rb/pull/48. Together with that,
this fixes #19177, #19866, #20223, #20302.
This commit is contained in:
parent
2992b7f519
commit
66737b5e82
@ -52,7 +52,6 @@ module ELFShim
|
||||
@interpreter = T.let(nil, T.nilable(String))
|
||||
@dynamic_elf = T.let(nil, T.nilable(T::Boolean))
|
||||
@metadata = T.let(nil, T.nilable(Metadata))
|
||||
@patchelf_patcher = nil
|
||||
|
||||
super
|
||||
end
|
||||
@ -123,7 +122,7 @@ module ELFShim
|
||||
# "/lib:/usr/lib:/usr/local/lib"
|
||||
sig { returns(T.nilable(String)) }
|
||||
def rpath
|
||||
@rpath ||= rpath_using_patchelf_rb
|
||||
metadata.rpath
|
||||
end
|
||||
|
||||
# An array of runtime search path entries, such as:
|
||||
@ -134,7 +133,7 @@ module ELFShim
|
||||
|
||||
sig { returns(T.nilable(String)) }
|
||||
def interpreter
|
||||
@interpreter ||= patchelf_patcher.interpreter
|
||||
metadata.interpreter
|
||||
end
|
||||
|
||||
def patch!(interpreter: nil, rpath: nil)
|
||||
@ -149,11 +148,12 @@ module ELFShim
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def dynamic_elf?
|
||||
@dynamic_elf ||= elf_parser.segment_by_type(:DYNAMIC).present?
|
||||
metadata.dynamic_elf?
|
||||
end
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def section_names
|
||||
@section_names ||= elf_parser.sections.map(&:name).compact_blank
|
||||
metadata.section_names
|
||||
end
|
||||
|
||||
# Helper class for reading metadata from an ELF file.
|
||||
@ -164,35 +164,53 @@ module ELFShim
|
||||
sig { returns(T.nilable(String)) }
|
||||
attr_reader :dylib_id
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def dynamic_elf?
|
||||
@dynamic_elf
|
||||
end
|
||||
|
||||
sig { returns(T.nilable(String)) }
|
||||
attr_reader :interpreter
|
||||
|
||||
sig { returns(T.nilable(String)) }
|
||||
attr_reader :rpath
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
attr_reader :dylibs
|
||||
attr_reader :section_names
|
||||
|
||||
sig { params(path: ELFShim).void }
|
||||
def initialize(path)
|
||||
@path = T.let(path, ELFShim)
|
||||
@dylibs = T.let([], T::Array[String])
|
||||
@dylib_id = T.let(nil, T.nilable(String))
|
||||
@dylib_id, needed = needed_libraries path
|
||||
@dylibs = needed.map { |lib| find_full_lib_path(lib).to_s } if needed.present?
|
||||
require "patchelf"
|
||||
patcher = path.patchelf_patcher
|
||||
|
||||
@metadata = T.let(nil, T.nilable(T::Hash[String, T.untyped]))
|
||||
@path = T.let(path, ELFShim)
|
||||
@dylibs = T.let(nil, T.nilable(T::Array[String]))
|
||||
@dylib_id = T.let(nil, T.nilable(String))
|
||||
|
||||
dynamic_segment = patcher.elf.segment_by_type(:dynamic)
|
||||
@dynamic_elf = dynamic_segment.present?
|
||||
@dylib_id, @needed = if @dynamic_elf
|
||||
[patcher.soname, patcher.needed]
|
||||
else
|
||||
[nil, []]
|
||||
end
|
||||
|
||||
@interpreter = patcher.interpreter
|
||||
@rpath = patcher.rpath || patcher.runpath
|
||||
@section_names = patcher.elf.sections.map(&:name).compact_blank
|
||||
|
||||
@dt_flags_1 = dynamic_segment&.tag_by_type(:flags_1)&.value
|
||||
end
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def dylibs
|
||||
@dylibs ||= @needed.map { |lib| find_full_lib_path(lib).to_s }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def needed_libraries(path)
|
||||
return [nil, []] unless path.dynamic_elf?
|
||||
|
||||
needed_libraries_using_patchelf_rb path
|
||||
end
|
||||
|
||||
def needed_libraries_using_patchelf_rb(path)
|
||||
patcher = path.patchelf_patcher
|
||||
[patcher.soname, patcher.needed]
|
||||
end
|
||||
|
||||
def find_full_lib_path(basename)
|
||||
local_paths = (path.patchelf_patcher.runpath || path.patchelf_patcher.rpath)&.split(":")
|
||||
local_paths = rpath&.split(":")
|
||||
|
||||
# Search for dependencies in the runpath/rpath first
|
||||
local_paths&.each do |local_path|
|
||||
@ -202,11 +220,10 @@ module ELFShim
|
||||
end
|
||||
|
||||
# Check if DF_1_NODEFLIB is set
|
||||
dt_flags_1 = path.elf_parser.segment_by_type(:dynamic)&.tag_by_type(:flags_1)
|
||||
nodeflib_flag = if dt_flags_1.nil?
|
||||
nodeflib_flag = if @dt_flags_1.nil?
|
||||
false
|
||||
else
|
||||
dt_flags_1.value & ELFTools::Constants::DF::DF_1_NODEFLIB != 0
|
||||
@dt_flags_1 & ELFTools::Constants::DF::DF_1_NODEFLIB != 0
|
||||
end
|
||||
|
||||
linker_library_paths = OS::Linux::Ld.library_paths
|
||||
@ -247,13 +264,12 @@ module ELFShim
|
||||
patcher.save(patchelf_compatible: true)
|
||||
end
|
||||
|
||||
def rpath_using_patchelf_rb
|
||||
patchelf_patcher.runpath || patchelf_patcher.rpath
|
||||
end
|
||||
|
||||
# Don't cache the patcher; it keeps the ELF file open so long as it is alive.
|
||||
# Instead, for read-only access to the ELF file's metadata, fetch it and cache
|
||||
# it with {Metadata}.
|
||||
def patchelf_patcher
|
||||
require "patchelf"
|
||||
@patchelf_patcher ||= ::PatchELF::Patcher.new to_s, on_error: :silent
|
||||
::PatchELF::Patcher.new to_s, on_error: :silent
|
||||
end
|
||||
|
||||
sig { returns(Metadata) }
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user