From 909606688a3e56c1f079d0e5d32dfd8521314316 Mon Sep 17 00:00:00 2001 From: Bo Anderson Date: Mon, 10 Jun 2024 23:11:33 +0100 Subject: [PATCH] macho/load_commands: support new macOS 15 dylib use command --- lib/macho/load_commands.rb | 91 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/lib/macho/load_commands.rb b/lib/macho/load_commands.rb index 183f6a6a2..da7c78055 100644 --- a/lib/macho/load_commands.rb +++ b/lib/macho/load_commands.rb @@ -195,6 +195,20 @@ module LoadCommands :SG_READ_ONLY => 0x10, }.freeze + # association of dylib use flag symbols to values + # @api private + DYLIB_USE_FLAGS = { + :DYLIB_USE_WEAK_LINK => 0x1, + :DYLIB_USE_REEXPORT => 0x2, + :DYLIB_USE_UPWARD => 0x4, + :DYLIB_USE_DELAYED_INIT => 0x8, + }.freeze + + # the marker used to denote a newer style dylib use command. + # the value is the timestamp 24 January 1984 18:12:16 + # @api private + DYLIB_USE_MARKER = 0x1a741800 + # The top-level Mach-O load command structure. # # This is the most generic load command -- only the type ID and size are @@ -528,6 +542,41 @@ class DylibCommand < LoadCommand # @return [Integer] the library's compatibility version number field :compatibility_version, :uint32 + # Instantiates a new DylibCommand or DylibUseCommand. + # macOS 15 and later use a new format for dylib commands (DylibUseCommand), + # which is determined based on a special timestamp and the name offset. + # @param view [MachO::MachOView] the load command's raw view + # @return [DylibCommand] the new dylib load command + # @api private + def self.new_from_bin(view) + dylib_command = super(view) + + if dylib_command.instance_of?(DylibCommand) && + dylib_command.timestamp == DYLIB_USE_MARKER && + dylib_command.name.to_i == DylibUseCommand.bytesize + DylibUseCommand.new_from_bin(view) + else + dylib_command + end + end + + # @example + # puts "this dylib is weakly loaded" if dylib_command.flag?(:DYLIB_USE_WEAK_LINK) + # @param flag [Symbol] a dylib use command flag symbol + # @return [Boolean] true if `flag` applies to this dylib command + def flag?(flag) + case cmd + when LOAD_COMMAND_CONSTANTS[:LC_LOAD_WEAK_DYLIB] + flag == :DYLIB_USE_WEAK_LINK + when LOAD_COMMAND_CONSTANTS[:LC_REEXPORT_DYLIB] + flag == :DYLIB_USE_REEXPORT + when LOAD_COMMAND_CONSTANTS[:LC_LOAD_UPWARD_DYLIB] + flag == :DYLIB_USE_UPWARD + else + false + end + end + # @param context [SerializationContext] # the context # @return [String] the serialized fields of the load command @@ -553,6 +602,48 @@ def to_h end end + # The newer format of load command representing some aspect of shared libraries, + # depending on filetype. Corresponds to LC_LOAD_DYLIB or LC_LOAD_WEAK_DYLIB. + class DylibUseCommand < DylibCommand + # @return [Integer] any flags associated with this dylib use command + field :flags, :uint32 + + alias marker timestamp + + # @example + # puts "this dylib is weakly loaded" if dylib_command.flag?(:DYLIB_USE_WEAK_LINK) + # @param flag [Symbol] a dylib use command flag symbol + # @return [Boolean] true if `flag` applies to this dylib command + def flag?(flag) + flag = DYLIB_USE_FLAGS[flag] + + return false if flag.nil? + + flags & flag == flag + end + + # @param context [SerializationContext] + # the context + # @return [String] the serialized fields of the load command + # @api private + def serialize(context) + format = Utils.specialize_format(self.class.format, context.endianness) + string_payload, string_offsets = Utils.pack_strings(self.class.bytesize, + context.alignment, + :name => name.to_s) + cmdsize = self.class.bytesize + string_payload.bytesize + [cmd, cmdsize, string_offsets[:name], marker, current_version, + compatibility_version, flags].pack(format) + string_payload + end + + # @return [Hash] a hash representation of this {DylibUseCommand} + def to_h + { + "flags" => flags, + }.merge super + end + end + # A load command representing some aspect of the dynamic linker, depending # on filetype. Corresponds to LC_ID_DYLINKER, LC_LOAD_DYLINKER, and # LC_DYLD_ENVIRONMENT.