ruby-macho 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
 - data/lib/cstruct.rb +346 -0
 - data/lib/int_helpers.rb +17 -0
 - data/lib/macho.rb +14 -0
 - data/lib/macho/exceptions.rb +54 -0
 - data/lib/macho/file.rb +351 -0
 - data/lib/macho/headers.rb +122 -0
 - data/lib/macho/load_commands.rb +588 -0
 - data/lib/macho/sections.rb +114 -0
 - data/lib/macho/structure.rb +15 -0
 - data/lib/macho/utils.rb +28 -0
 - data/lib/otool_helpers.rb +3 -0
 - metadata +55 -0
 
    
        checksums.yaml
    ADDED
    
    | 
         @@ -0,0 +1,7 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ---
         
     | 
| 
      
 2 
     | 
    
         
            +
            SHA1:
         
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 686c8eeee9cd964f7c1112259c64ba6f37650262
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 9c7a05e36acc9b2120263e3847ca0dbb8e8f9fd9
         
     | 
| 
      
 5 
     | 
    
         
            +
            SHA512:
         
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: ff579faa1788421e587e995b4434ff2e8ef0f5491b33c4d599577ea70fff6ac7ce3b3e4eea166d5e5e94cd5128f3050adab90b38949fdc854419fbacfa059aa2
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 107c76d944b6aa7987ac1c0299f2d49ee8e48eb8a4b8a9fa046ea9fbd6821586910c0892792c14c3aefb094b02d0a2a60c8d811cddc2c3f73309933f330c6b2c
         
     | 
    
        data/lib/cstruct.rb
    ADDED
    
    | 
         @@ -0,0 +1,346 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Struct does some trickery with custom allocators so we can't
         
     | 
| 
      
 2 
     | 
    
         
            +
            # subclass it without writing C.  Instead we define a CStruct class
         
     | 
| 
      
 3 
     | 
    
         
            +
            # that does something similar enough for our purpose.  It is
         
     | 
| 
      
 4 
     | 
    
         
            +
            # subclassed just like any other class.  A nice side-effect of this
         
     | 
| 
      
 5 
     | 
    
         
            +
            # syntax is that it is always clear that a CStruct is just a class and
         
     | 
| 
      
 6 
     | 
    
         
            +
            # instances of the struct are objects.
         
     | 
| 
      
 7 
     | 
    
         
            +
            #    
         
     | 
| 
      
 8 
     | 
    
         
            +
            # Some light metaprogramming is used to make the following syntax possible:
         
     | 
| 
      
 9 
     | 
    
         
            +
            #
         
     | 
| 
      
 10 
     | 
    
         
            +
            # class MachHeader < CStruct
         
     | 
| 
      
 11 
     | 
    
         
            +
            #   uint :magic
         
     | 
| 
      
 12 
     | 
    
         
            +
            #   int  :cputype
         
     | 
| 
      
 13 
     | 
    
         
            +
            #   int  :cpusubtype
         
     | 
| 
      
 14 
     | 
    
         
            +
            #    ...
         
     | 
| 
      
 15 
     | 
    
         
            +
            #   int  :flags
         
     | 
| 
      
 16 
     | 
    
         
            +
            # end
         
     | 
| 
      
 17 
     | 
    
         
            +
            #
         
     | 
| 
      
 18 
     | 
    
         
            +
            # Inheritance works as you would expect.
         
     | 
| 
      
 19 
     | 
    
         
            +
            #
         
     | 
| 
      
 20 
     | 
    
         
            +
            # class LoadCommand < CStruct
         
     | 
| 
      
 21 
     | 
    
         
            +
            #   uint32 :cmd
         
     | 
| 
      
 22 
     | 
    
         
            +
            #   uint32 :cmdsize
         
     | 
| 
      
 23 
     | 
    
         
            +
            # end
         
     | 
| 
      
 24 
     | 
    
         
            +
            # 
         
     | 
| 
      
 25 
     | 
    
         
            +
            # # inherits cmd and cmdsize as the first 2 fields
         
     | 
| 
      
 26 
     | 
    
         
            +
            # class SegmentCommand < LoadCommand
         
     | 
| 
      
 27 
     | 
    
         
            +
            #   string :segname, 16
         
     | 
| 
      
 28 
     | 
    
         
            +
            #   uint32 :vmaddr
         
     | 
| 
      
 29 
     | 
    
         
            +
            #   uint32 
         
     | 
| 
      
 30 
     | 
    
         
            +
            # end
         
     | 
| 
      
 31 
     | 
    
         
            +
            #
         
     | 
| 
      
 32 
     | 
    
         
            +
            # Nothing tricky or confusing there.  Members of a CStruct class are
         
     | 
| 
      
 33 
     | 
    
         
            +
            # declared in the class definition.  A different definition using a
         
     | 
| 
      
 34 
     | 
    
         
            +
            # more static approach probably wouldn't be very hard...  if
         
     | 
| 
      
 35 
     | 
    
         
            +
            # performance is critical ... but then why are you using Ruby? ;-)
         
     | 
| 
      
 36 
     | 
    
         
            +
            #
         
     | 
| 
      
 37 
     | 
    
         
            +
            #
         
     | 
| 
      
 38 
     | 
    
         
            +
            # TODO support bit fields
         
     | 
| 
      
 39 
     | 
    
         
            +
            #
         
     | 
| 
      
 40 
     | 
    
         
            +
            # Bit fields should be supported by passing the number of bits a field
         
     | 
| 
      
 41 
     | 
    
         
            +
            # should occupy. Perhaps we could use the size 'pack' for the rest of
         
     | 
| 
      
 42 
     | 
    
         
            +
            # the field.
         
     | 
| 
      
 43 
     | 
    
         
            +
            #
         
     | 
| 
      
 44 
     | 
    
         
            +
            # class RelocationInfo < CStruct
         
     | 
| 
      
 45 
     | 
    
         
            +
            #   int32  :address
         
     | 
| 
      
 46 
     | 
    
         
            +
            #   uint32 :symbolnum, 24
         
     | 
| 
      
 47 
     | 
    
         
            +
            #   pack   :pcrel,      1
         
     | 
| 
      
 48 
     | 
    
         
            +
            #   pack   :length,     2
         
     | 
| 
      
 49 
     | 
    
         
            +
            #   pack   :extern,     1
         
     | 
| 
      
 50 
     | 
    
         
            +
            #   pack   :type,       4
         
     | 
| 
      
 51 
     | 
    
         
            +
            # end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
            class CStruct
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
              ###################
         
     | 
| 
      
 57 
     | 
    
         
            +
              # Class Constants #
         
     | 
| 
      
 58 
     | 
    
         
            +
              ###################
         
     | 
| 
      
 59 
     | 
    
         
            +
              
         
     | 
| 
      
 60 
     | 
    
         
            +
              # Size in bytes.
         
     | 
| 
      
 61 
     | 
    
         
            +
              SizeMap = {
         
     | 
| 
      
 62 
     | 
    
         
            +
                :int8   => 1,
         
     | 
| 
      
 63 
     | 
    
         
            +
                :uint8  => 1,
         
     | 
| 
      
 64 
     | 
    
         
            +
                :int16  => 2,
         
     | 
| 
      
 65 
     | 
    
         
            +
                :uint16 => 2,
         
     | 
| 
      
 66 
     | 
    
         
            +
                :int32  => 4,
         
     | 
| 
      
 67 
     | 
    
         
            +
                :uint32 => 4,
         
     | 
| 
      
 68 
     | 
    
         
            +
                :int64  => 8,
         
     | 
| 
      
 69 
     | 
    
         
            +
                :uint64 => 8,
         
     | 
| 
      
 70 
     | 
    
         
            +
                :string => lambda { |*opts| opts.first }, # first opt is size
         
     | 
| 
      
 71 
     | 
    
         
            +
                # the last 3 are to make the language more C-like
         
     | 
| 
      
 72 
     | 
    
         
            +
                :int    => 4,
         
     | 
| 
      
 73 
     | 
    
         
            +
                :uint   => 4,
         
     | 
| 
      
 74 
     | 
    
         
            +
                :char   => 1
         
     | 
| 
      
 75 
     | 
    
         
            +
              }
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
              # 32-bit
         
     | 
| 
      
 78 
     | 
    
         
            +
              PackMap = {
         
     | 
| 
      
 79 
     | 
    
         
            +
                :int8   => 'c',
         
     | 
| 
      
 80 
     | 
    
         
            +
                :uint8  => 'C',
         
     | 
| 
      
 81 
     | 
    
         
            +
                :int16  => 's',
         
     | 
| 
      
 82 
     | 
    
         
            +
                :uint16 => 'S',
         
     | 
| 
      
 83 
     | 
    
         
            +
                :int32  => 'i',
         
     | 
| 
      
 84 
     | 
    
         
            +
                :uint32 => 'I',
         
     | 
| 
      
 85 
     | 
    
         
            +
                :int64  => 'q',
         
     | 
| 
      
 86 
     | 
    
         
            +
                :uint64 => 'Q',
         
     | 
| 
      
 87 
     | 
    
         
            +
                :string => lambda do |str, *opts|
         
     | 
| 
      
 88 
     | 
    
         
            +
                                    len = opts.first
         
     | 
| 
      
 89 
     | 
    
         
            +
                                    str.ljust(len, "\0")[0, len]
         
     | 
| 
      
 90 
     | 
    
         
            +
                                  end,
         
     | 
| 
      
 91 
     | 
    
         
            +
                # a few C-like names
         
     | 
| 
      
 92 
     | 
    
         
            +
                :int    => 'i',
         
     | 
| 
      
 93 
     | 
    
         
            +
                :uint   => 'I',
         
     | 
| 
      
 94 
     | 
    
         
            +
                :char   => 'C'
         
     | 
| 
      
 95 
     | 
    
         
            +
              }
         
     | 
| 
      
 96 
     | 
    
         
            +
              
         
     | 
| 
      
 97 
     | 
    
         
            +
              # Only needed when unpacking is different from packing, i.e. strings w/ lambdas in PackMap.
         
     | 
| 
      
 98 
     | 
    
         
            +
              UnpackMap = {
         
     | 
| 
      
 99 
     | 
    
         
            +
                :string => lambda do |str, *opts|
         
     | 
| 
      
 100 
     | 
    
         
            +
                                    len = opts.first
         
     | 
| 
      
 101 
     | 
    
         
            +
                                    val = str[0, len-1].sub(/\0*$/, '')
         
     | 
| 
      
 102 
     | 
    
         
            +
                                    str.slice!((len-1)..-1)
         
     | 
| 
      
 103 
     | 
    
         
            +
                                    val
         
     | 
| 
      
 104 
     | 
    
         
            +
                                  end
         
     | 
| 
      
 105 
     | 
    
         
            +
              }
         
     | 
| 
      
 106 
     | 
    
         
            +
              
         
     | 
| 
      
 107 
     | 
    
         
            +
              ##########################
         
     | 
| 
      
 108 
     | 
    
         
            +
              # Class Instance Methods #
         
     | 
| 
      
 109 
     | 
    
         
            +
              ##########################
         
     | 
| 
      
 110 
     | 
    
         
            +
              
         
     | 
| 
      
 111 
     | 
    
         
            +
              # Note: const_get and const_set are used so the constants are bound
         
     | 
| 
      
 112 
     | 
    
         
            +
              #       at runtime, to the real class that has subclassed CStruct.
         
     | 
| 
      
 113 
     | 
    
         
            +
              #       I figured Ruby would do this but I haven't looked at the
         
     | 
| 
      
 114 
     | 
    
         
            +
              #       implementation of constants so it might be tricky.
         
     | 
| 
      
 115 
     | 
    
         
            +
              #
         
     | 
| 
      
 116 
     | 
    
         
            +
              #       All of this could probably be avoided with Ruby 1.9 and
         
     | 
| 
      
 117 
     | 
    
         
            +
              #       private class variables.  That is definitely something to
         
     | 
| 
      
 118 
     | 
    
         
            +
              #       experiment with.
         
     | 
| 
      
 119 
     | 
    
         
            +
              
         
     | 
| 
      
 120 
     | 
    
         
            +
              class <<self
         
     | 
| 
      
 121 
     | 
    
         
            +
                
         
     | 
| 
      
 122 
     | 
    
         
            +
                def inherited(subclass)
         
     | 
| 
      
 123 
     | 
    
         
            +
                  subclass.instance_eval do
         
     | 
| 
      
 124 
     | 
    
         
            +
                    
         
     | 
| 
      
 125 
     | 
    
         
            +
                    # These "constants" are only constant references.  Structs can
         
     | 
| 
      
 126 
     | 
    
         
            +
                    # be modified.  After the struct is defined it is still open,
         
     | 
| 
      
 127 
     | 
    
         
            +
                    # but good practice would be not to change a struct after it
         
     | 
| 
      
 128 
     | 
    
         
            +
                    # has been defined.
         
     | 
| 
      
 129 
     | 
    
         
            +
                    # 
         
     | 
| 
      
 130 
     | 
    
         
            +
                    # To support inheritance properly we try to get these
         
     | 
| 
      
 131 
     | 
    
         
            +
                    # constants from the enclosing scope (and clone them before
         
     | 
| 
      
 132 
     | 
    
         
            +
                    # modifying them!), and default to empty, er, defaults.
         
     | 
| 
      
 133 
     | 
    
         
            +
                    
         
     | 
| 
      
 134 
     | 
    
         
            +
                    members = const_get(:Members).clone rescue []
         
     | 
| 
      
 135 
     | 
    
         
            +
                    member_index = const_get(:MemberIndex).clone rescue {}
         
     | 
| 
      
 136 
     | 
    
         
            +
                    member_sizes = const_get(:MemberSizes).clone rescue {}
         
     | 
| 
      
 137 
     | 
    
         
            +
                    member_opts = const_get(:MemberOptions).clone rescue {}
         
     | 
| 
      
 138 
     | 
    
         
            +
                    
         
     | 
| 
      
 139 
     | 
    
         
            +
                    const_set(:Members, members)
         
     | 
| 
      
 140 
     | 
    
         
            +
                    const_set(:MemberIndex, member_index)
         
     | 
| 
      
 141 
     | 
    
         
            +
                    const_set(:MemberSizes, member_sizes)
         
     | 
| 
      
 142 
     | 
    
         
            +
                    const_set(:MemberOptions, member_opts)
         
     | 
| 
      
 143 
     | 
    
         
            +
                    
         
     | 
| 
      
 144 
     | 
    
         
            +
                  end
         
     | 
| 
      
 145 
     | 
    
         
            +
                end
         
     | 
| 
      
 146 
     | 
    
         
            +
             
     | 
| 
      
 147 
     | 
    
         
            +
             
     | 
| 
      
 148 
     | 
    
         
            +
                # Define a method for each size name, and when that method is called it updates
         
     | 
| 
      
 149 
     | 
    
         
            +
                # the struct class accordingly.
         
     | 
| 
      
 150 
     | 
    
         
            +
                SizeMap.keys.each do |type|
         
     | 
| 
      
 151 
     | 
    
         
            +
                  
         
     | 
| 
      
 152 
     | 
    
         
            +
                  define_method(type) do |name, *args|
         
     | 
| 
      
 153 
     | 
    
         
            +
                    name = name.to_sym
         
     | 
| 
      
 154 
     | 
    
         
            +
                    const_get(:MemberIndex)[name] = const_get(:Members).size
         
     | 
| 
      
 155 
     | 
    
         
            +
                    const_get(:MemberSizes)[name] = type
         
     | 
| 
      
 156 
     | 
    
         
            +
                    const_get(:MemberOptions)[name] = args
         
     | 
| 
      
 157 
     | 
    
         
            +
                    const_get(:Members) << name
         
     | 
| 
      
 158 
     | 
    
         
            +
                  end
         
     | 
| 
      
 159 
     | 
    
         
            +
                  
         
     | 
| 
      
 160 
     | 
    
         
            +
                end
         
     | 
| 
      
 161 
     | 
    
         
            +
                
         
     | 
| 
      
 162 
     | 
    
         
            +
                
         
     | 
| 
      
 163 
     | 
    
         
            +
                # Return the number of members.
         
     | 
| 
      
 164 
     | 
    
         
            +
                def size
         
     | 
| 
      
 165 
     | 
    
         
            +
                  const_get(:Members).size
         
     | 
| 
      
 166 
     | 
    
         
            +
                end
         
     | 
| 
      
 167 
     | 
    
         
            +
                alias_method :length, :size
         
     | 
| 
      
 168 
     | 
    
         
            +
                
         
     | 
| 
      
 169 
     | 
    
         
            +
                # Return the number of bytes occupied in memory or on disk.
         
     | 
| 
      
 170 
     | 
    
         
            +
                def bytesize
         
     | 
| 
      
 171 
     | 
    
         
            +
                  const_get(:Members).inject(0) { |size, name| size + sizeof(name) }
         
     | 
| 
      
 172 
     | 
    
         
            +
                end
         
     | 
| 
      
 173 
     | 
    
         
            +
                    
         
     | 
| 
      
 174 
     | 
    
         
            +
                def sizeof(name)
         
     | 
| 
      
 175 
     | 
    
         
            +
                  value = SizeMap[const_get(:MemberSizes)[name]]
         
     | 
| 
      
 176 
     | 
    
         
            +
                  value.respond_to?(:call) ? value.call(*const_get(:MemberOptions)[name]) : value
         
     | 
| 
      
 177 
     | 
    
         
            +
                end
         
     | 
| 
      
 178 
     | 
    
         
            +
             
     | 
| 
      
 179 
     | 
    
         
            +
                def new_from_bin(bin)
         
     | 
| 
      
 180 
     | 
    
         
            +
                  new_struct = new
         
     | 
| 
      
 181 
     | 
    
         
            +
                  new_struct.unserialize(bin)
         
     | 
| 
      
 182 
     | 
    
         
            +
                end
         
     | 
| 
      
 183 
     | 
    
         
            +
             
     | 
| 
      
 184 
     | 
    
         
            +
              end
         
     | 
| 
      
 185 
     | 
    
         
            +
              
         
     | 
| 
      
 186 
     | 
    
         
            +
             
     | 
| 
      
 187 
     | 
    
         
            +
              ####################
         
     | 
| 
      
 188 
     | 
    
         
            +
              # Instance Methods #
         
     | 
| 
      
 189 
     | 
    
         
            +
              ####################
         
     | 
| 
      
 190 
     | 
    
         
            +
              
         
     | 
| 
      
 191 
     | 
    
         
            +
              attr_reader :values
         
     | 
| 
      
 192 
     | 
    
         
            +
              
         
     | 
| 
      
 193 
     | 
    
         
            +
              def initialize(*args)
         
     | 
| 
      
 194 
     | 
    
         
            +
                @values = args
         
     | 
| 
      
 195 
     | 
    
         
            +
              end
         
     | 
| 
      
 196 
     | 
    
         
            +
             
     | 
| 
      
 197 
     | 
    
         
            +
              def serialize
         
     | 
| 
      
 198 
     | 
    
         
            +
                vals = @values.clone
         
     | 
| 
      
 199 
     | 
    
         
            +
                membs = members.clone
         
     | 
| 
      
 200 
     | 
    
         
            +
                pack_pattern.map do |patt|
         
     | 
| 
      
 201 
     | 
    
         
            +
                  name = membs.shift
         
     | 
| 
      
 202 
     | 
    
         
            +
                  if patt.is_a?(String)
         
     | 
| 
      
 203 
     | 
    
         
            +
                    [vals.shift].pack(patt)
         
     | 
| 
      
 204 
     | 
    
         
            +
                  else
         
     | 
| 
      
 205 
     | 
    
         
            +
                    patt.call(vals.shift, *member_options[name])
         
     | 
| 
      
 206 
     | 
    
         
            +
                  end      
         
     | 
| 
      
 207 
     | 
    
         
            +
                end.join
         
     | 
| 
      
 208 
     | 
    
         
            +
              end
         
     | 
| 
      
 209 
     | 
    
         
            +
             
     | 
| 
      
 210 
     | 
    
         
            +
              def unserialize(bin)
         
     | 
| 
      
 211 
     | 
    
         
            +
                bin = bin.clone
         
     | 
| 
      
 212 
     | 
    
         
            +
                @values = []
         
     | 
| 
      
 213 
     | 
    
         
            +
                membs = members.clone
         
     | 
| 
      
 214 
     | 
    
         
            +
                unpack_pattern.each do |patt|
         
     | 
| 
      
 215 
     | 
    
         
            +
                  name = membs.shift
         
     | 
| 
      
 216 
     | 
    
         
            +
                  if patt.is_a?(String)
         
     | 
| 
      
 217 
     | 
    
         
            +
                    @values += bin.unpack(patt)
         
     | 
| 
      
 218 
     | 
    
         
            +
                    bin.slice!(0, sizeof(name))
         
     | 
| 
      
 219 
     | 
    
         
            +
                  else
         
     | 
| 
      
 220 
     | 
    
         
            +
                    @values << patt.call(bin, *member_options[name])
         
     | 
| 
      
 221 
     | 
    
         
            +
                  end
         
     | 
| 
      
 222 
     | 
    
         
            +
                end
         
     | 
| 
      
 223 
     | 
    
         
            +
                self
         
     | 
| 
      
 224 
     | 
    
         
            +
              end
         
     | 
| 
      
 225 
     | 
    
         
            +
              
         
     | 
| 
      
 226 
     | 
    
         
            +
              def pack_pattern
         
     | 
| 
      
 227 
     | 
    
         
            +
                members.map { |name| PackMap[member_sizes[name]] }
         
     | 
| 
      
 228 
     | 
    
         
            +
              end
         
     | 
| 
      
 229 
     | 
    
         
            +
              
         
     | 
| 
      
 230 
     | 
    
         
            +
              def unpack_pattern
         
     | 
| 
      
 231 
     | 
    
         
            +
                members.map { |name| UnpackMap[member_sizes[name]] || PackMap[member_sizes[name]] }
         
     | 
| 
      
 232 
     | 
    
         
            +
              end
         
     | 
| 
      
 233 
     | 
    
         
            +
              
         
     | 
| 
      
 234 
     | 
    
         
            +
              def [](name_or_idx)
         
     | 
| 
      
 235 
     | 
    
         
            +
                case name_or_idx
         
     | 
| 
      
 236 
     | 
    
         
            +
                  
         
     | 
| 
      
 237 
     | 
    
         
            +
                when Numeric
         
     | 
| 
      
 238 
     | 
    
         
            +
                  idx = name_or_idx
         
     | 
| 
      
 239 
     | 
    
         
            +
                  @values[idx]
         
     | 
| 
      
 240 
     | 
    
         
            +
                  
         
     | 
| 
      
 241 
     | 
    
         
            +
                when String, Symbol
         
     | 
| 
      
 242 
     | 
    
         
            +
                  name = name_or_idx.to_sym
         
     | 
| 
      
 243 
     | 
    
         
            +
                  @values[member_index[name]]
         
     | 
| 
      
 244 
     | 
    
         
            +
                  
         
     | 
| 
      
 245 
     | 
    
         
            +
                else
         
     | 
| 
      
 246 
     | 
    
         
            +
                  raise ArgumentError, "expected name or index, got #{name_or_idx.inspect}"
         
     | 
| 
      
 247 
     | 
    
         
            +
                end
         
     | 
| 
      
 248 
     | 
    
         
            +
              end
         
     | 
| 
      
 249 
     | 
    
         
            +
              
         
     | 
| 
      
 250 
     | 
    
         
            +
              def []=(name_or_idx, value)
         
     | 
| 
      
 251 
     | 
    
         
            +
                case name_or_idx
         
     | 
| 
      
 252 
     | 
    
         
            +
                  
         
     | 
| 
      
 253 
     | 
    
         
            +
                when Numeric
         
     | 
| 
      
 254 
     | 
    
         
            +
                  idx = name_or_idx
         
     | 
| 
      
 255 
     | 
    
         
            +
                  @values[idx] = value
         
     | 
| 
      
 256 
     | 
    
         
            +
                  
         
     | 
| 
      
 257 
     | 
    
         
            +
                when String, Symbol
         
     | 
| 
      
 258 
     | 
    
         
            +
                  name = name_or_idx.to_sym
         
     | 
| 
      
 259 
     | 
    
         
            +
                  @values[member_index[name]] = value
         
     | 
| 
      
 260 
     | 
    
         
            +
                  
         
     | 
| 
      
 261 
     | 
    
         
            +
                else
         
     | 
| 
      
 262 
     | 
    
         
            +
                  raise ArgumentError, "expected name or index, got #{name_or_idx.inspect}"
         
     | 
| 
      
 263 
     | 
    
         
            +
                end
         
     | 
| 
      
 264 
     | 
    
         
            +
              end
         
     | 
| 
      
 265 
     | 
    
         
            +
              
         
     | 
| 
      
 266 
     | 
    
         
            +
              def ==(other)
         
     | 
| 
      
 267 
     | 
    
         
            +
                puts @values.inspect
         
     | 
| 
      
 268 
     | 
    
         
            +
                puts other.values.inspect
         
     | 
| 
      
 269 
     | 
    
         
            +
                other.is_a?(self.class) && other.values == @values
         
     | 
| 
      
 270 
     | 
    
         
            +
              end
         
     | 
| 
      
 271 
     | 
    
         
            +
             
     | 
| 
      
 272 
     | 
    
         
            +
              # Some of these are just to quack like Ruby's built-in Struct.  YAGNI, but can't hurt either.
         
     | 
| 
      
 273 
     | 
    
         
            +
             
     | 
| 
      
 274 
     | 
    
         
            +
              def each(&block)
         
     | 
| 
      
 275 
     | 
    
         
            +
                @values.each(&block)
         
     | 
| 
      
 276 
     | 
    
         
            +
              end
         
     | 
| 
      
 277 
     | 
    
         
            +
              
         
     | 
| 
      
 278 
     | 
    
         
            +
              def each_pair(&block)
         
     | 
| 
      
 279 
     | 
    
         
            +
                members.zip(@values).each(&block)
         
     | 
| 
      
 280 
     | 
    
         
            +
              end
         
     | 
| 
      
 281 
     | 
    
         
            +
              
         
     | 
| 
      
 282 
     | 
    
         
            +
              def size
         
     | 
| 
      
 283 
     | 
    
         
            +
                members.size
         
     | 
| 
      
 284 
     | 
    
         
            +
              end
         
     | 
| 
      
 285 
     | 
    
         
            +
              alias_method :length, :size
         
     | 
| 
      
 286 
     | 
    
         
            +
              
         
     | 
| 
      
 287 
     | 
    
         
            +
              def sizeof(name)
         
     | 
| 
      
 288 
     | 
    
         
            +
                self.class.sizeof(name)
         
     | 
| 
      
 289 
     | 
    
         
            +
              end
         
     | 
| 
      
 290 
     | 
    
         
            +
             
     | 
| 
      
 291 
     | 
    
         
            +
              def bytesize
         
     | 
| 
      
 292 
     | 
    
         
            +
                self.class.bytesize
         
     | 
| 
      
 293 
     | 
    
         
            +
              end
         
     | 
| 
      
 294 
     | 
    
         
            +
             
     | 
| 
      
 295 
     | 
    
         
            +
              alias_method :to_a, :values
         
     | 
| 
      
 296 
     | 
    
         
            +
             
     | 
| 
      
 297 
     | 
    
         
            +
             
     | 
| 
      
 298 
     | 
    
         
            +
              # A few convenience methods.
         
     | 
| 
      
 299 
     | 
    
         
            +
             
     | 
| 
      
 300 
     | 
    
         
            +
              def members
         
     | 
| 
      
 301 
     | 
    
         
            +
                self.class::Members
         
     | 
| 
      
 302 
     | 
    
         
            +
              end
         
     | 
| 
      
 303 
     | 
    
         
            +
              
         
     | 
| 
      
 304 
     | 
    
         
            +
              def member_index
         
     | 
| 
      
 305 
     | 
    
         
            +
                self.class::MemberIndex
         
     | 
| 
      
 306 
     | 
    
         
            +
              end
         
     | 
| 
      
 307 
     | 
    
         
            +
              
         
     | 
| 
      
 308 
     | 
    
         
            +
              def member_sizes
         
     | 
| 
      
 309 
     | 
    
         
            +
                self.class::MemberSizes
         
     | 
| 
      
 310 
     | 
    
         
            +
              end
         
     | 
| 
      
 311 
     | 
    
         
            +
             
         
     | 
| 
      
 312 
     | 
    
         
            +
              def member_options
         
     | 
| 
      
 313 
     | 
    
         
            +
                self.class::MemberOptions
         
     | 
| 
      
 314 
     | 
    
         
            +
              end
         
     | 
| 
      
 315 
     | 
    
         
            +
             
         
     | 
| 
      
 316 
     | 
    
         
            +
              # The last expression is returned, so return self instead of junk.
         
     | 
| 
      
 317 
     | 
    
         
            +
              self  
         
     | 
| 
      
 318 
     | 
    
         
            +
            end
         
     | 
| 
      
 319 
     | 
    
         
            +
             
     | 
| 
      
 320 
     | 
    
         
            +
             
     | 
| 
      
 321 
     | 
    
         
            +
            # a small test
         
     | 
| 
      
 322 
     | 
    
         
            +
            if $0 == __FILE__
         
     | 
| 
      
 323 
     | 
    
         
            +
              class MachHeader < CStruct
         
     | 
| 
      
 324 
     | 
    
         
            +
                uint :magic
         
     | 
| 
      
 325 
     | 
    
         
            +
                int  :cputype
         
     | 
| 
      
 326 
     | 
    
         
            +
                int  :cpusubtype
         
     | 
| 
      
 327 
     | 
    
         
            +
                string :segname, 16
         
     | 
| 
      
 328 
     | 
    
         
            +
              end
         
     | 
| 
      
 329 
     | 
    
         
            +
              puts MachHeader::Members.inspect
         
     | 
| 
      
 330 
     | 
    
         
            +
              puts MachHeader::MemberIndex.inspect
         
     | 
| 
      
 331 
     | 
    
         
            +
              puts MachHeader::MemberSizes.inspect
         
     | 
| 
      
 332 
     | 
    
         
            +
              puts "# of MachHeader members: " + MachHeader.size.to_s + ", size in bytes: " + MachHeader.bytesize.to_s
         
     | 
| 
      
 333 
     | 
    
         
            +
              mh = MachHeader.new(0xfeedface, 7, 3, "foobar")
         
     | 
| 
      
 334 
     | 
    
         
            +
              %w[magic cputype cpusubtype segname].each do |field|
         
     | 
| 
      
 335 
     | 
    
         
            +
                puts "#{field}(#{MachHeader.sizeof(field.to_sym)}):      #{mh[field.to_sym].inspect}"
         
     | 
| 
      
 336 
     | 
    
         
            +
              end
         
     | 
| 
      
 337 
     | 
    
         
            +
              puts mh.pack_pattern.inspect
         
     | 
| 
      
 338 
     | 
    
         
            +
              binstr = mh.serialize
         
     | 
| 
      
 339 
     | 
    
         
            +
              puts "values: " + mh.values.inspect
         
     | 
| 
      
 340 
     | 
    
         
            +
              newmh = MachHeader.new_from_bin(binstr)
         
     | 
| 
      
 341 
     | 
    
         
            +
              puts "new values: " + newmh.values.inspect
         
     | 
| 
      
 342 
     | 
    
         
            +
              newbinstr = newmh.serialize
         
     | 
| 
      
 343 
     | 
    
         
            +
              puts "serialized:   " + binstr.inspect
         
     | 
| 
      
 344 
     | 
    
         
            +
              puts "unserialized: " + newbinstr.inspect
         
     | 
| 
      
 345 
     | 
    
         
            +
              puts "new == old ? " + (newbinstr == binstr).to_s
         
     | 
| 
      
 346 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/int_helpers.rb
    ADDED
    
    | 
         @@ -0,0 +1,17 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module INTHelpers
         
     | 
| 
      
 2 
     | 
    
         
            +
            	Change = Struct.new("Changes", :old, :new)
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            	Rpath = Struct.new("Rpaths", :old, :new, :found) do
         
     | 
| 
      
 5 
     | 
    
         
            +
            		def found?
         
     | 
| 
      
 6 
     | 
    
         
            +
            			found
         
     | 
| 
      
 7 
     | 
    
         
            +
            		end
         
     | 
| 
      
 8 
     | 
    
         
            +
            	end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            	AddRpath = Struct.new("AddRpaths", :new)
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            	DeleteRpath = Struct.new("DeleteRpaths", :old, :found) do
         
     | 
| 
      
 13 
     | 
    
         
            +
            		def found?
         
     | 
| 
      
 14 
     | 
    
         
            +
            			found
         
     | 
| 
      
 15 
     | 
    
         
            +
            		end
         
     | 
| 
      
 16 
     | 
    
         
            +
            	end
         
     | 
| 
      
 17 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/macho.rb
    ADDED
    
    | 
         @@ -0,0 +1,14 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            $:.unshift File.dirname(__FILE__)
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "cstruct"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "macho/headers"
         
     | 
| 
      
 5 
     | 
    
         
            +
            require "macho/structure"
         
     | 
| 
      
 6 
     | 
    
         
            +
            require "macho/load_commands"
         
     | 
| 
      
 7 
     | 
    
         
            +
            require "macho/sections"
         
     | 
| 
      
 8 
     | 
    
         
            +
            require "macho/file"
         
     | 
| 
      
 9 
     | 
    
         
            +
            require "macho/exceptions"
         
     | 
| 
      
 10 
     | 
    
         
            +
            require "macho/utils"
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            module MachO
         
     | 
| 
      
 13 
     | 
    
         
            +
            	# nothing to see here.
         
     | 
| 
      
 14 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,54 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module MachO
         
     | 
| 
      
 2 
     | 
    
         
            +
            	# generic toplevel error
         
     | 
| 
      
 3 
     | 
    
         
            +
            	class MachOError < RuntimeError
         
     | 
| 
      
 4 
     | 
    
         
            +
            	end
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            	# raised when a file's magic bytes are not valid mach-o magic
         
     | 
| 
      
 7 
     | 
    
         
            +
            	class MagicError < MachOError
         
     | 
| 
      
 8 
     | 
    
         
            +
            		def initialize(num)
         
     | 
| 
      
 9 
     | 
    
         
            +
            			super "Unrecognized Mach-O magic: 0x#{"%02x" % num}"
         
     | 
| 
      
 10 
     | 
    
         
            +
            		end
         
     | 
| 
      
 11 
     | 
    
         
            +
            	end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            	# raised when a file's magic bytes are those of a fat binary
         
     | 
| 
      
 14 
     | 
    
         
            +
            	class FatBinaryError < MachOError
         
     | 
| 
      
 15 
     | 
    
         
            +
            		def initialize(num)
         
     | 
| 
      
 16 
     | 
    
         
            +
            			super "Unsupported fat binary (magic 0x#{"%02x" % num})"
         
     | 
| 
      
 17 
     | 
    
         
            +
            		end
         
     | 
| 
      
 18 
     | 
    
         
            +
            	end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
            	class CPUTypeError < MachOError
         
     | 
| 
      
 21 
     | 
    
         
            +
            		def initialize(num)
         
     | 
| 
      
 22 
     | 
    
         
            +
            			super "Unrecognized CPU type: 0x#{"%02x" % num}"
         
     | 
| 
      
 23 
     | 
    
         
            +
            		end
         
     | 
| 
      
 24 
     | 
    
         
            +
            	end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
            	class CPUSubtypeError < MachOError
         
     | 
| 
      
 27 
     | 
    
         
            +
            		def initialize(num)
         
     | 
| 
      
 28 
     | 
    
         
            +
            			super "Unrecognized CPU sub-type: 0x#{"%02x" % num}"
         
     | 
| 
      
 29 
     | 
    
         
            +
            		end
         
     | 
| 
      
 30 
     | 
    
         
            +
            	end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
            	# raised when a mach-o file's filetype field is unknown
         
     | 
| 
      
 33 
     | 
    
         
            +
            	class FiletypeError < MachOError
         
     | 
| 
      
 34 
     | 
    
         
            +
            		def initialize(num)
         
     | 
| 
      
 35 
     | 
    
         
            +
            			super "Unrecognized Mach-O filetype code: 0x#{"%02x" % num}"
         
     | 
| 
      
 36 
     | 
    
         
            +
            		end
         
     | 
| 
      
 37 
     | 
    
         
            +
            	end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
            	# raised when an unknown load command is encountered
         
     | 
| 
      
 40 
     | 
    
         
            +
            	class LoadCommandError < MachOError
         
     | 
| 
      
 41 
     | 
    
         
            +
            		def initialize(num)
         
     | 
| 
      
 42 
     | 
    
         
            +
            			super "Unrecognized Mach-O load command: 0x#{"%02x" % num}"
         
     | 
| 
      
 43 
     | 
    
         
            +
            		end
         
     | 
| 
      
 44 
     | 
    
         
            +
            	end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
            	# raised when load commands are too large to fit in the current file
         
     | 
| 
      
 47 
     | 
    
         
            +
            	class HeaderPadError < MachOError
         
     | 
| 
      
 48 
     | 
    
         
            +
            		def initialize(filename)
         
     | 
| 
      
 49 
     | 
    
         
            +
            			super "Updated load commands do not fit in the header of " +
         
     | 
| 
      
 50 
     | 
    
         
            +
            			"#{filename}. #{filename} needs to be relinked, possibly with " +
         
     | 
| 
      
 51 
     | 
    
         
            +
            			"-headerpad or -headerpad_max_install_names"
         
     | 
| 
      
 52 
     | 
    
         
            +
            		end
         
     | 
| 
      
 53 
     | 
    
         
            +
            	end
         
     | 
| 
      
 54 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/macho/file.rb
    ADDED
    
    | 
         @@ -0,0 +1,351 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module MachO
         
     | 
| 
      
 2 
     | 
    
         
            +
            	class MachOFile
         
     | 
| 
      
 3 
     | 
    
         
            +
            		attr_reader :header, :load_commands
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            		def initialize(filename)
         
     | 
| 
      
 6 
     | 
    
         
            +
            			raise ArgumentError.new("filename must be a String") unless filename.is_a? String
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            			@filename = filename
         
     | 
| 
      
 9 
     | 
    
         
            +
            			@raw_data = open(@filename, "rb") { |f| f.read }
         
     | 
| 
      
 10 
     | 
    
         
            +
            			@header = get_mach_header
         
     | 
| 
      
 11 
     | 
    
         
            +
            			@load_commands = get_load_commands
         
     | 
| 
      
 12 
     | 
    
         
            +
            		end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            		def magic32?
         
     | 
| 
      
 15 
     | 
    
         
            +
            			Utils.magic32?(header[:magic])
         
     | 
| 
      
 16 
     | 
    
         
            +
            		end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
            		def magic64?
         
     | 
| 
      
 19 
     | 
    
         
            +
            			Utils.magic64?(header[:magic])
         
     | 
| 
      
 20 
     | 
    
         
            +
            		end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            		# is the file executable?
         
     | 
| 
      
 23 
     | 
    
         
            +
            		def executable?
         
     | 
| 
      
 24 
     | 
    
         
            +
            			header[:filetype] == MH_EXECUTE
         
     | 
| 
      
 25 
     | 
    
         
            +
            		end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            		# is the file a dynamically bound shared object?
         
     | 
| 
      
 28 
     | 
    
         
            +
            		def dylib?
         
     | 
| 
      
 29 
     | 
    
         
            +
            			header[:filetype] == MH_DYLIB
         
     | 
| 
      
 30 
     | 
    
         
            +
            		end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
            		# is the file a dynamically bound bundle?
         
     | 
| 
      
 33 
     | 
    
         
            +
            		def bundle?
         
     | 
| 
      
 34 
     | 
    
         
            +
            			header[:filetype] == MH_BUNDLE
         
     | 
| 
      
 35 
     | 
    
         
            +
            		end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
            		def magic
         
     | 
| 
      
 38 
     | 
    
         
            +
            			header[:magic]
         
     | 
| 
      
 39 
     | 
    
         
            +
            		end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
            		# string representation of the header's magic bytes
         
     | 
| 
      
 42 
     | 
    
         
            +
            		def magic_string
         
     | 
| 
      
 43 
     | 
    
         
            +
            			MH_MAGICS[header[:magic]]
         
     | 
| 
      
 44 
     | 
    
         
            +
            		end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
            		# string representation of the header's filetype field
         
     | 
| 
      
 47 
     | 
    
         
            +
            		def filetype
         
     | 
| 
      
 48 
     | 
    
         
            +
            			MH_FILETYPES[header[:filetype]]
         
     | 
| 
      
 49 
     | 
    
         
            +
            		end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
            		# string representation of the header's cputype field
         
     | 
| 
      
 52 
     | 
    
         
            +
            		def cputype
         
     | 
| 
      
 53 
     | 
    
         
            +
            			CPU_TYPES[header[:cputype]]
         
     | 
| 
      
 54 
     | 
    
         
            +
            		end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
            		# string representation of the header's cpusubtype field
         
     | 
| 
      
 57 
     | 
    
         
            +
            		def cpusubtype
         
     | 
| 
      
 58 
     | 
    
         
            +
            			CPU_SUBTYPES[header[:cpusubtype]]
         
     | 
| 
      
 59 
     | 
    
         
            +
            		end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
            		# number of load commands in the header
         
     | 
| 
      
 62 
     | 
    
         
            +
            		def ncmds
         
     | 
| 
      
 63 
     | 
    
         
            +
            			header[:ncmds]
         
     | 
| 
      
 64 
     | 
    
         
            +
            		end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
            		# size of all load commands
         
     | 
| 
      
 67 
     | 
    
         
            +
            		def sizeofcmds
         
     | 
| 
      
 68 
     | 
    
         
            +
            			header[:sizeofcmds]
         
     | 
| 
      
 69 
     | 
    
         
            +
            		end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
            		# various execution flags
         
     | 
| 
      
 72 
     | 
    
         
            +
            		def flags
         
     | 
| 
      
 73 
     | 
    
         
            +
            			header[:flags]
         
     | 
| 
      
 74 
     | 
    
         
            +
            		end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
            		# get load commands by name
         
     | 
| 
      
 77 
     | 
    
         
            +
            		def command(name)
         
     | 
| 
      
 78 
     | 
    
         
            +
            			load_commands.select { |lc| lc.to_s == name }
         
     | 
| 
      
 79 
     | 
    
         
            +
            		end
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
            		alias :[] :command
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
            		# get all segment commands
         
     | 
| 
      
 84 
     | 
    
         
            +
            		def segments
         
     | 
| 
      
 85 
     | 
    
         
            +
            			if magic32?
         
     | 
| 
      
 86 
     | 
    
         
            +
            				command("LC_SEGMENT")
         
     | 
| 
      
 87 
     | 
    
         
            +
            			else
         
     | 
| 
      
 88 
     | 
    
         
            +
            				command("LC_SEGMENT_64")
         
     | 
| 
      
 89 
     | 
    
         
            +
            			end
         
     | 
| 
      
 90 
     | 
    
         
            +
            		end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
            		# get the file's dylib id, if it is a dylib
         
     | 
| 
      
 93 
     | 
    
         
            +
            		def dylib_id
         
     | 
| 
      
 94 
     | 
    
         
            +
            			if !dylib?
         
     | 
| 
      
 95 
     | 
    
         
            +
            				return nil
         
     | 
| 
      
 96 
     | 
    
         
            +
            			end
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
            			dylib_id_cmd = command('LC_ID_DYLIB').first
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
            			cmdsize = dylib_id_cmd.cmdsize
         
     | 
| 
      
 101 
     | 
    
         
            +
            			offset = dylib_id_cmd.offset
         
     | 
| 
      
 102 
     | 
    
         
            +
            			stroffset = dylib_id_cmd.name
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
            			dylib_id = @raw_data.slice(offset + stroffset...offset + cmdsize).unpack("Z*").first
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
            			dylib_id
         
     | 
| 
      
 107 
     | 
    
         
            +
            		end
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
            		def dylib_id=(new_id)
         
     | 
| 
      
 110 
     | 
    
         
            +
            			if !new_id.is_a?(String)
         
     | 
| 
      
 111 
     | 
    
         
            +
            				raise ArgumentError.new("argument must be a String")
         
     | 
| 
      
 112 
     | 
    
         
            +
            			end
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
      
 114 
     | 
    
         
            +
            			if !dylib?
         
     | 
| 
      
 115 
     | 
    
         
            +
            				return nil
         
     | 
| 
      
 116 
     | 
    
         
            +
            			end
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
            			if magic32?
         
     | 
| 
      
 119 
     | 
    
         
            +
            				cmd_round = 4
         
     | 
| 
      
 120 
     | 
    
         
            +
            			else
         
     | 
| 
      
 121 
     | 
    
         
            +
            				cmd_round = 8
         
     | 
| 
      
 122 
     | 
    
         
            +
            			end
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
            			new_sizeofcmds = header[:sizeofcmds]
         
     | 
| 
      
 125 
     | 
    
         
            +
            			dylib_id_cmd = command('LC_ID_DYLIB').first
         
     | 
| 
      
 126 
     | 
    
         
            +
            			old_id = dylib_id
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
            			new_pad = Utils.round(new_id.size, cmd_round) - new_id.size
         
     | 
| 
      
 129 
     | 
    
         
            +
            			old_pad = Utils.round(old_id.size, cmd_round) - old_id.size
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
            			# pad the old and new IDs with null bytes to meet command bounds
         
     | 
| 
      
 132 
     | 
    
         
            +
            			old_id << "\x00" * old_pad
         
     | 
| 
      
 133 
     | 
    
         
            +
            			new_id << "\x00" * new_pad
         
     | 
| 
      
 134 
     | 
    
         
            +
             
     | 
| 
      
 135 
     | 
    
         
            +
            			# calculate the new size of the DylibCommand and sizeofcmds in MH
         
     | 
| 
      
 136 
     | 
    
         
            +
            			new_size = DylibCommand.bytesize + new_id.size
         
     | 
| 
      
 137 
     | 
    
         
            +
            			new_sizeofcmds += new_size - dylib_id_cmd.cmdsize
         
     | 
| 
      
 138 
     | 
    
         
            +
             
     | 
| 
      
 139 
     | 
    
         
            +
            			# calculate the low file offset (offset to first section data)
         
     | 
| 
      
 140 
     | 
    
         
            +
            			low_fileoff = 2**64 # ULLONGMAX
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
      
 142 
     | 
    
         
            +
            			segments.each do |seg|
         
     | 
| 
      
 143 
     | 
    
         
            +
            				sections(seg).each do |sect|
         
     | 
| 
      
 144 
     | 
    
         
            +
            					if sect.size != 0 && !sect.flag?(S_ZEROFILL) &&
         
     | 
| 
      
 145 
     | 
    
         
            +
            							!sect.flag?(S_THREAD_LOCAL_ZEROFILL) &&
         
     | 
| 
      
 146 
     | 
    
         
            +
            							sect.offset < low_fileoff
         
     | 
| 
      
 147 
     | 
    
         
            +
             
     | 
| 
      
 148 
     | 
    
         
            +
            						low_fileoff = sect.offset
         
     | 
| 
      
 149 
     | 
    
         
            +
            					end
         
     | 
| 
      
 150 
     | 
    
         
            +
            				end
         
     | 
| 
      
 151 
     | 
    
         
            +
            			end
         
     | 
| 
      
 152 
     | 
    
         
            +
             
     | 
| 
      
 153 
     | 
    
         
            +
            			if new_sizeofcmds + header.bytesize > low_fileoff
         
     | 
| 
      
 154 
     | 
    
         
            +
            				raise HeaderPadError.new(@filename)
         
     | 
| 
      
 155 
     | 
    
         
            +
            			end
         
     | 
| 
      
 156 
     | 
    
         
            +
             
     | 
| 
      
 157 
     | 
    
         
            +
            			# update sizeofcmds in mach_header
         
     | 
| 
      
 158 
     | 
    
         
            +
            			set_sizeofcmds(new_sizeofcmds)
         
     | 
| 
      
 159 
     | 
    
         
            +
             
     | 
| 
      
 160 
     | 
    
         
            +
            			# update cmdsize in the dylib_command
         
     | 
| 
      
 161 
     | 
    
         
            +
            			@raw_data[dylib_id_cmd.offset + 4, 4] = [new_size].pack("V")
         
     | 
| 
      
 162 
     | 
    
         
            +
             
     | 
| 
      
 163 
     | 
    
         
            +
            			# delete the old id
         
     | 
| 
      
 164 
     | 
    
         
            +
            			@raw_data.slice!(dylib_id_cmd.offset + dylib_id_cmd.name...dylib_id_cmd.offset + dylib_id_cmd.cmdsize)
         
     | 
| 
      
 165 
     | 
    
         
            +
             
     | 
| 
      
 166 
     | 
    
         
            +
            			# insert the new id
         
     | 
| 
      
 167 
     | 
    
         
            +
            			@raw_data.insert(dylib_id_cmd.offset + dylib_id_cmd.name, new_id)
         
     | 
| 
      
 168 
     | 
    
         
            +
             
     | 
| 
      
 169 
     | 
    
         
            +
            			# pad/unpad after new_sizeofcmds until offsets are corrected
         
     | 
| 
      
 170 
     | 
    
         
            +
            			null_pad = old_id.size - new_id.size
         
     | 
| 
      
 171 
     | 
    
         
            +
             
     | 
| 
      
 172 
     | 
    
         
            +
            			if null_pad < 0
         
     | 
| 
      
 173 
     | 
    
         
            +
            				@raw_data.slice!(new_sizeofcmds + header.bytesize, null_pad.abs)
         
     | 
| 
      
 174 
     | 
    
         
            +
            			else
         
     | 
| 
      
 175 
     | 
    
         
            +
            				@raw_data.insert(new_sizeofcmds + header.bytesize, "\x00" * null_pad)
         
     | 
| 
      
 176 
     | 
    
         
            +
            			end
         
     | 
| 
      
 177 
     | 
    
         
            +
             
     | 
| 
      
 178 
     | 
    
         
            +
            			# synchronize fields with the raw data
         
     | 
| 
      
 179 
     | 
    
         
            +
            			header = get_mach_header
         
     | 
| 
      
 180 
     | 
    
         
            +
            			load_commands = get_load_commands
         
     | 
| 
      
 181 
     | 
    
         
            +
            		end
         
     | 
| 
      
 182 
     | 
    
         
            +
             
     | 
| 
      
 183 
     | 
    
         
            +
            		# get a list of dylib paths linked to this file
         
     | 
| 
      
 184 
     | 
    
         
            +
            		def linked_dylibs
         
     | 
| 
      
 185 
     | 
    
         
            +
            			dylibs = []
         
     | 
| 
      
 186 
     | 
    
         
            +
            			dylib_cmds = command('LC_LOAD_DYLIB')
         
     | 
| 
      
 187 
     | 
    
         
            +
             
     | 
| 
      
 188 
     | 
    
         
            +
            			dylib_cmds.each do |dylib_cmd|
         
     | 
| 
      
 189 
     | 
    
         
            +
            				cmdsize = dylib_cmd.cmdsize
         
     | 
| 
      
 190 
     | 
    
         
            +
            				offset = dylib_cmd.offset
         
     | 
| 
      
 191 
     | 
    
         
            +
            				stroffset = dylib_cmd.name
         
     | 
| 
      
 192 
     | 
    
         
            +
             
     | 
| 
      
 193 
     | 
    
         
            +
            				dylib = @raw_data.slice(offset + stroffset...offset + cmdsize).unpack("Z*").first
         
     | 
| 
      
 194 
     | 
    
         
            +
             
     | 
| 
      
 195 
     | 
    
         
            +
            				dylibs << dylib
         
     | 
| 
      
 196 
     | 
    
         
            +
            			end
         
     | 
| 
      
 197 
     | 
    
         
            +
             
     | 
| 
      
 198 
     | 
    
         
            +
            			dylibs
         
     | 
| 
      
 199 
     | 
    
         
            +
            		end
         
     | 
| 
      
 200 
     | 
    
         
            +
             
     | 
| 
      
 201 
     | 
    
         
            +
            		# get all sections in a segment by name
         
     | 
| 
      
 202 
     | 
    
         
            +
            		def sections(segment)
         
     | 
| 
      
 203 
     | 
    
         
            +
            			sections = []
         
     | 
| 
      
 204 
     | 
    
         
            +
             
     | 
| 
      
 205 
     | 
    
         
            +
            			if !segment.is_a?(SegmentCommand) && !segment.is_a?(SegmentCommand64)
         
     | 
| 
      
 206 
     | 
    
         
            +
            				raise ArgumentError.new("not a valid segment")
         
     | 
| 
      
 207 
     | 
    
         
            +
            			end
         
     | 
| 
      
 208 
     | 
    
         
            +
             
     | 
| 
      
 209 
     | 
    
         
            +
            			if segment.nsects.zero?
         
     | 
| 
      
 210 
     | 
    
         
            +
            				return sections
         
     | 
| 
      
 211 
     | 
    
         
            +
            			end
         
     | 
| 
      
 212 
     | 
    
         
            +
             
     | 
| 
      
 213 
     | 
    
         
            +
            			offset = segment.offset + segment.class.bytesize
         
     | 
| 
      
 214 
     | 
    
         
            +
             
     | 
| 
      
 215 
     | 
    
         
            +
            			segment.nsects.times do
         
     | 
| 
      
 216 
     | 
    
         
            +
            				if segment.is_a? SegmentCommand
         
     | 
| 
      
 217 
     | 
    
         
            +
            					sections << Section.new_from_bin(@raw_data.slice(offset, Section.bytesize))
         
     | 
| 
      
 218 
     | 
    
         
            +
            					offset += Section.bytesize
         
     | 
| 
      
 219 
     | 
    
         
            +
            				else
         
     | 
| 
      
 220 
     | 
    
         
            +
            					sections << Section64.new_from_bin(@raw_data.slice(offset, Section64.bytesize))
         
     | 
| 
      
 221 
     | 
    
         
            +
            					offset += Section64.bytesize
         
     | 
| 
      
 222 
     | 
    
         
            +
            				end
         
     | 
| 
      
 223 
     | 
    
         
            +
            			end
         
     | 
| 
      
 224 
     | 
    
         
            +
             
     | 
| 
      
 225 
     | 
    
         
            +
            			sections
         
     | 
| 
      
 226 
     | 
    
         
            +
            		end
         
     | 
| 
      
 227 
     | 
    
         
            +
             
     | 
| 
      
 228 
     | 
    
         
            +
            		def write(filename)
         
     | 
| 
      
 229 
     | 
    
         
            +
            			File.open(filename, "wb") { |f| f.write(@raw_data) }
         
     | 
| 
      
 230 
     | 
    
         
            +
            		end
         
     | 
| 
      
 231 
     | 
    
         
            +
             
     | 
| 
      
 232 
     | 
    
         
            +
            		def write!
         
     | 
| 
      
 233 
     | 
    
         
            +
            			File.open(@filename, "wb") { |f| f.write(@raw_data) }
         
     | 
| 
      
 234 
     | 
    
         
            +
            		end
         
     | 
| 
      
 235 
     | 
    
         
            +
             
     | 
| 
      
 236 
     | 
    
         
            +
            		#######
         
     | 
| 
      
 237 
     | 
    
         
            +
            		private
         
     | 
| 
      
 238 
     | 
    
         
            +
             
     | 
| 
      
 239 
     | 
    
         
            +
            		def get_mach_header
         
     | 
| 
      
 240 
     | 
    
         
            +
            			magic = get_magic
         
     | 
| 
      
 241 
     | 
    
         
            +
            			cputype = get_cputype
         
     | 
| 
      
 242 
     | 
    
         
            +
            			cpusubtype = get_cpusubtype
         
     | 
| 
      
 243 
     | 
    
         
            +
            			filetype = get_filetype
         
     | 
| 
      
 244 
     | 
    
         
            +
            			ncmds = get_ncmds
         
     | 
| 
      
 245 
     | 
    
         
            +
            			sizeofcmds = get_sizeofcmds
         
     | 
| 
      
 246 
     | 
    
         
            +
            			flags = get_flags
         
     | 
| 
      
 247 
     | 
    
         
            +
            			
         
     | 
| 
      
 248 
     | 
    
         
            +
            			if Utils.magic32?(magic)
         
     | 
| 
      
 249 
     | 
    
         
            +
            				header = MachHeader.new(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags)
         
     | 
| 
      
 250 
     | 
    
         
            +
            			else
         
     | 
| 
      
 251 
     | 
    
         
            +
            				# the reserved field is reserved, so just fill it with 0
         
     | 
| 
      
 252 
     | 
    
         
            +
            				header = MachHeader64.new(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags, 0)
         
     | 
| 
      
 253 
     | 
    
         
            +
            			end
         
     | 
| 
      
 254 
     | 
    
         
            +
            		end
         
     | 
| 
      
 255 
     | 
    
         
            +
             
     | 
| 
      
 256 
     | 
    
         
            +
            		def get_magic
         
     | 
| 
      
 257 
     | 
    
         
            +
            			magic = @raw_data[0..3].unpack("N").first
         
     | 
| 
      
 258 
     | 
    
         
            +
             
     | 
| 
      
 259 
     | 
    
         
            +
            			if !Utils.magic?(magic)
         
     | 
| 
      
 260 
     | 
    
         
            +
            				raise MagicError.new(magic)
         
     | 
| 
      
 261 
     | 
    
         
            +
            			end
         
     | 
| 
      
 262 
     | 
    
         
            +
            			
         
     | 
| 
      
 263 
     | 
    
         
            +
            			# TODO: support fat (universal) binaries
         
     | 
| 
      
 264 
     | 
    
         
            +
            			if Utils.fat_magic?(magic)
         
     | 
| 
      
 265 
     | 
    
         
            +
            				raise FatBinaryError.new(magic)
         
     | 
| 
      
 266 
     | 
    
         
            +
            			end
         
     | 
| 
      
 267 
     | 
    
         
            +
             
     | 
| 
      
 268 
     | 
    
         
            +
            			magic
         
     | 
| 
      
 269 
     | 
    
         
            +
            		end
         
     | 
| 
      
 270 
     | 
    
         
            +
             
     | 
| 
      
 271 
     | 
    
         
            +
            		def get_cputype
         
     | 
| 
      
 272 
     | 
    
         
            +
            			cputype = @raw_data[4..7].unpack("V").first
         
     | 
| 
      
 273 
     | 
    
         
            +
             
     | 
| 
      
 274 
     | 
    
         
            +
            			if !CPU_TYPES.keys.include?(cputype)
         
     | 
| 
      
 275 
     | 
    
         
            +
            				raise CPUTypeError.new(cputype)
         
     | 
| 
      
 276 
     | 
    
         
            +
            			end
         
     | 
| 
      
 277 
     | 
    
         
            +
             
     | 
| 
      
 278 
     | 
    
         
            +
            			cputype
         
     | 
| 
      
 279 
     | 
    
         
            +
            		end
         
     | 
| 
      
 280 
     | 
    
         
            +
             
     | 
| 
      
 281 
     | 
    
         
            +
            		def get_cpusubtype
         
     | 
| 
      
 282 
     | 
    
         
            +
            			cpusubtype = @raw_data[8..11].unpack("V").first
         
     | 
| 
      
 283 
     | 
    
         
            +
             
     | 
| 
      
 284 
     | 
    
         
            +
            			# this mask isn't documented!
         
     | 
| 
      
 285 
     | 
    
         
            +
            			cpusubtype &= ~CPU_SUBTYPE_LIB64
         
     | 
| 
      
 286 
     | 
    
         
            +
             
     | 
| 
      
 287 
     | 
    
         
            +
            			if !CPU_SUBTYPES.keys.include?(cpusubtype)
         
     | 
| 
      
 288 
     | 
    
         
            +
            				raise CPUSubtypeError.new(cpusubtype)
         
     | 
| 
      
 289 
     | 
    
         
            +
            			end
         
     | 
| 
      
 290 
     | 
    
         
            +
             
     | 
| 
      
 291 
     | 
    
         
            +
            			cpusubtype
         
     | 
| 
      
 292 
     | 
    
         
            +
            		end
         
     | 
| 
      
 293 
     | 
    
         
            +
             
     | 
| 
      
 294 
     | 
    
         
            +
            		def get_filetype
         
     | 
| 
      
 295 
     | 
    
         
            +
            			filetype = @raw_data[12..15].unpack("V").first
         
     | 
| 
      
 296 
     | 
    
         
            +
             
     | 
| 
      
 297 
     | 
    
         
            +
            			if !MH_FILETYPES.keys.include?(filetype)
         
     | 
| 
      
 298 
     | 
    
         
            +
            				raise FiletypeError.new(filetype)
         
     | 
| 
      
 299 
     | 
    
         
            +
            			end
         
     | 
| 
      
 300 
     | 
    
         
            +
             
     | 
| 
      
 301 
     | 
    
         
            +
            			filetype
         
     | 
| 
      
 302 
     | 
    
         
            +
            		end
         
     | 
| 
      
 303 
     | 
    
         
            +
             
     | 
| 
      
 304 
     | 
    
         
            +
            		def get_ncmds
         
     | 
| 
      
 305 
     | 
    
         
            +
            			ncmds = @raw_data[16..19].unpack("V").first
         
     | 
| 
      
 306 
     | 
    
         
            +
             
     | 
| 
      
 307 
     | 
    
         
            +
            			ncmds
         
     | 
| 
      
 308 
     | 
    
         
            +
            		end
         
     | 
| 
      
 309 
     | 
    
         
            +
             
     | 
| 
      
 310 
     | 
    
         
            +
            		def get_sizeofcmds
         
     | 
| 
      
 311 
     | 
    
         
            +
            			sizeofcmds = @raw_data[20..23].unpack("V").first
         
     | 
| 
      
 312 
     | 
    
         
            +
             
     | 
| 
      
 313 
     | 
    
         
            +
            			sizeofcmds
         
     | 
| 
      
 314 
     | 
    
         
            +
            		end
         
     | 
| 
      
 315 
     | 
    
         
            +
             
     | 
| 
      
 316 
     | 
    
         
            +
            		# TODO: parse flags, maybe?
         
     | 
| 
      
 317 
     | 
    
         
            +
            		def get_flags
         
     | 
| 
      
 318 
     | 
    
         
            +
            			flags = @raw_data[24..27].unpack("V").first
         
     | 
| 
      
 319 
     | 
    
         
            +
             
     | 
| 
      
 320 
     | 
    
         
            +
            			flags
         
     | 
| 
      
 321 
     | 
    
         
            +
            		end
         
     | 
| 
      
 322 
     | 
    
         
            +
             
     | 
| 
      
 323 
     | 
    
         
            +
            		def get_load_commands
         
     | 
| 
      
 324 
     | 
    
         
            +
            			offset = header.bytesize
         
     | 
| 
      
 325 
     | 
    
         
            +
            			load_commands = []
         
     | 
| 
      
 326 
     | 
    
         
            +
             
     | 
| 
      
 327 
     | 
    
         
            +
            			header[:ncmds].times do
         
     | 
| 
      
 328 
     | 
    
         
            +
            				cmd = @raw_data.slice(offset, 4).unpack("V").first
         
     | 
| 
      
 329 
     | 
    
         
            +
             
     | 
| 
      
 330 
     | 
    
         
            +
            				if !LC_STRUCTURES.has_key?(cmd)
         
     | 
| 
      
 331 
     | 
    
         
            +
            					raise LoadCommandError.new(cmd)
         
     | 
| 
      
 332 
     | 
    
         
            +
            				end
         
     | 
| 
      
 333 
     | 
    
         
            +
             
     | 
| 
      
 334 
     | 
    
         
            +
            				# why do I do this? i don't like declaring constants below
         
     | 
| 
      
 335 
     | 
    
         
            +
            				# classes, and i need them to resolve...
         
     | 
| 
      
 336 
     | 
    
         
            +
            				klass = Object.const_get "MachO::#{LC_STRUCTURES[cmd]}"
         
     | 
| 
      
 337 
     | 
    
         
            +
            				command = klass.new_from_bin(offset, @raw_data.slice(offset, klass.bytesize))
         
     | 
| 
      
 338 
     | 
    
         
            +
             
     | 
| 
      
 339 
     | 
    
         
            +
            				load_commands << command
         
     | 
| 
      
 340 
     | 
    
         
            +
            				offset += command.cmdsize
         
     | 
| 
      
 341 
     | 
    
         
            +
            			end
         
     | 
| 
      
 342 
     | 
    
         
            +
             
     | 
| 
      
 343 
     | 
    
         
            +
            			load_commands
         
     | 
| 
      
 344 
     | 
    
         
            +
            		end
         
     | 
| 
      
 345 
     | 
    
         
            +
             
     | 
| 
      
 346 
     | 
    
         
            +
            		def set_sizeofcmds(size)
         
     | 
| 
      
 347 
     | 
    
         
            +
            			new_size = [size].pack("V")
         
     | 
| 
      
 348 
     | 
    
         
            +
            			@raw_data[20..23] = new_size
         
     | 
| 
      
 349 
     | 
    
         
            +
            		end
         
     | 
| 
      
 350 
     | 
    
         
            +
            	end
         
     | 
| 
      
 351 
     | 
    
         
            +
            end
         
     |