@@ -111,7 +111,7 @@ module LoadCommands
111
111
# "reserved for internal use only", no public struct
112
112
:LC_PREPAGE => "LoadCommand" ,
113
113
:LC_DYSYMTAB => "DysymtabCommand" ,
114
- :LC_LOAD_DYLIB => "DylibCommand " ,
114
+ :LC_LOAD_DYLIB => "DylibUseCommand " ,
115
115
:LC_ID_DYLIB => "DylibCommand" ,
116
116
:LC_LOAD_DYLINKER => "DylinkerCommand" ,
117
117
:LC_ID_DYLINKER => "DylinkerCommand" ,
@@ -123,7 +123,7 @@ module LoadCommands
123
123
:LC_SUB_LIBRARY => "SubLibraryCommand" ,
124
124
:LC_TWOLEVEL_HINTS => "TwolevelHintsCommand" ,
125
125
:LC_PREBIND_CKSUM => "PrebindCksumCommand" ,
126
- :LC_LOAD_WEAK_DYLIB => "DylibCommand " ,
126
+ :LC_LOAD_WEAK_DYLIB => "DylibUseCommand " ,
127
127
:LC_SEGMENT_64 => "SegmentCommand64" ,
128
128
:LC_ROUTINES_64 => "RoutinesCommand64" ,
129
129
:LC_UUID => "UUIDCommand" ,
@@ -195,6 +195,20 @@ module LoadCommands
195
195
:SG_READ_ONLY => 0x10 ,
196
196
} . freeze
197
197
198
+ # association of dylib use flag symbols to values
199
+ # @api private
200
+ DYLIB_USE_FLAGS = {
201
+ :DYLIB_USE_WEAK_LINK => 0x1 ,
202
+ :DYLIB_USE_REEXPORT => 0x2 ,
203
+ :DYLIB_USE_UPWARD => 0x4 ,
204
+ :DYLIB_USE_DELAYED_INIT => 0x8 ,
205
+ } . freeze
206
+
207
+ # the marker used to denote a newer style dylib use command.
208
+ # the value is the timestamp 24 January 1984 18:12:16
209
+ # @api private
210
+ DYLIB_USE_MARKER = 0x1a741800
211
+
198
212
# The top-level Mach-O load command structure.
199
213
#
200
214
# This is the most generic load command -- only the type ID and size are
@@ -228,11 +242,19 @@ def self.create(cmd_sym, *args)
228
242
raise LoadCommandNotCreatableError , cmd_sym unless CREATABLE_LOAD_COMMANDS . include? ( cmd_sym )
229
243
230
244
klass = LoadCommands . const_get LC_STRUCTURES [ cmd_sym ]
231
- cmd = LOAD_COMMAND_CONSTANTS [ cmd_sym ]
232
245
233
246
# cmd will be filled in, view and cmdsize will be left unpopulated
234
247
klass_arity = klass . min_args - 3
235
248
249
+ # macOS 15 introduces a new dylib load command that adds a flags field to the end.
250
+ # It uses the same commands with it dynamically being created if the dylib has a flags field
251
+ if klass == DylibUseCommand && ( args [ 1 ] != DYLIB_USE_MARKER || args . size <= DylibCommand . min_args - 3 )
252
+ klass = DylibCommand
253
+ klass_arity = klass . min_args - 3
254
+ end
255
+
256
+ cmd = LOAD_COMMAND_CONSTANTS [ cmd_sym ]
257
+
236
258
raise LoadCommandCreationArityError . new ( cmd_sym , klass_arity , args . size ) if klass_arity > args . size
237
259
238
260
klass . new ( nil , cmd , nil , *args )
@@ -528,6 +550,23 @@ class DylibCommand < LoadCommand
528
550
# @return [Integer] the library's compatibility version number
529
551
field :compatibility_version , :uint32
530
552
553
+ # @example
554
+ # puts "this dylib is weakly loaded" if dylib_command.flag?(:DYLIB_USE_WEAK_LINK)
555
+ # @param flag [Symbol] a dylib use command flag symbol
556
+ # @return [Boolean] true if `flag` applies to this dylib command
557
+ def flag? ( flag )
558
+ case cmd
559
+ when LOAD_COMMAND_CONSTANTS [ :LC_LOAD_WEAK_DYLIB ]
560
+ flag == :DYLIB_USE_WEAK_LINK
561
+ when LOAD_COMMAND_CONSTANTS [ :LC_REEXPORT_DYLIB ]
562
+ flag == :DYLIB_USE_REEXPORT
563
+ when LOAD_COMMAND_CONSTANTS [ :LC_LOAD_UPWARD_DYLIB ]
564
+ flag == :DYLIB_USE_UPWARD
565
+ else
566
+ false
567
+ end
568
+ end
569
+
531
570
# @param context [SerializationContext]
532
571
# the context
533
572
# @return [String] the serialized fields of the load command
@@ -553,6 +592,65 @@ def to_h
553
592
end
554
593
end
555
594
595
+ # The newer format of load command representing some aspect of shared libraries,
596
+ # depending on filetype. Corresponds to LC_LOAD_DYLIB or LC_LOAD_WEAK_DYLIB.
597
+ class DylibUseCommand < DylibCommand
598
+ # @return [Integer] any flags associated with this dylib use command
599
+ field :flags , :uint32
600
+
601
+ alias marker timestamp
602
+
603
+ # Instantiates a new DylibCommand or DylibUseCommand.
604
+ # macOS 15 and later use a new format for dylib commands (DylibUseCommand),
605
+ # which is determined based on a special timestamp and the name offset.
606
+ # @param view [MachO::MachOView] the load command's raw view
607
+ # @return [DylibCommand] the new dylib load command
608
+ # @api private
609
+ def self . new_from_bin ( view )
610
+ dylib_command = DylibCommand . new_from_bin ( view )
611
+
612
+ if dylib_command . timestamp == DYLIB_USE_MARKER &&
613
+ dylib_command . name . to_i == DylibUseCommand . bytesize
614
+ super ( view )
615
+ else
616
+ dylib_command
617
+ end
618
+ end
619
+
620
+ # @example
621
+ # puts "this dylib is weakly loaded" if dylib_command.flag?(:DYLIB_USE_WEAK_LINK)
622
+ # @param flag [Symbol] a dylib use command flag symbol
623
+ # @return [Boolean] true if `flag` applies to this dylib command
624
+ def flag? ( flag )
625
+ flag = DYLIB_USE_FLAGS [ flag ]
626
+
627
+ return false if flag . nil?
628
+
629
+ flags & flag == flag
630
+ end
631
+
632
+ # @param context [SerializationContext]
633
+ # the context
634
+ # @return [String] the serialized fields of the load command
635
+ # @api private
636
+ def serialize ( context )
637
+ format = Utils . specialize_format ( self . class . format , context . endianness )
638
+ string_payload , string_offsets = Utils . pack_strings ( self . class . bytesize ,
639
+ context . alignment ,
640
+ :name => name . to_s )
641
+ cmdsize = self . class . bytesize + string_payload . bytesize
642
+ [ cmd , cmdsize , string_offsets [ :name ] , marker , current_version ,
643
+ compatibility_version , flags ] . pack ( format ) + string_payload
644
+ end
645
+
646
+ # @return [Hash] a hash representation of this {DylibUseCommand}
647
+ def to_h
648
+ {
649
+ "flags" => flags ,
650
+ } . merge super
651
+ end
652
+ end
653
+
556
654
# A load command representing some aspect of the dynamic linker, depending
557
655
# on filetype. Corresponds to LC_ID_DYLINKER, LC_LOAD_DYLINKER, and
558
656
# LC_DYLD_ENVIRONMENT.
0 commit comments