elftools 2.0.0 → 2.2.0

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.
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'bindata'
4
4
 
5
+ require 'elftools/exceptions'
6
+
5
7
  module ELFTools
6
8
  # Define ELF related structures in this module.
7
9
  #
@@ -15,18 +17,93 @@ module ELFTools
15
17
  { selection: :elf_class, choices: { 32 => :"#{t}32", 64 => :"#{t}64" }, copy_on_change: true }
16
18
  end
17
19
 
20
+ # How an integer of each width is packed, for +String#unpack+, whether it
21
+ # records a sign or not.
22
+ UNPACK_TEMPLATES = {
23
+ little: {
24
+ false => { 1 => 'C', 2 => 'v', 4 => 'V', 8 => 'Q<' },
25
+ true => { 1 => 'c', 2 => 's<', 4 => 'l<', 8 => 'q<' }
26
+ },
27
+ big: {
28
+ false => { 1 => 'C', 2 => 'n', 4 => 'N', 8 => 'Q>' },
29
+ true => { 1 => 'c', 2 => 's>', 4 => 'l>', 8 => 'q>' }
30
+ }
31
+ }.freeze
32
+
33
+ # Bytes of set bits to read a prototype from, longer than any structure
34
+ # here, so that reading one never runs short of them.
35
+ SET_BITS = ("\xff" * 256).b.freeze
36
+
18
37
  attr_accessor :elf_class # @return [Integer] 32 or 64.
19
38
  attr_accessor :offset # @return [Integer] The file offset of this header.
20
39
 
21
- # Records which fields have been patched.
22
- # @return [Hash{Integer => Integer}] Patches.
40
+ # Reads the structure, remembering the bytes it was read from.
41
+ #
42
+ # They are taken back off the stream. A stream that cannot be seeked is
43
+ # serialized instead.
44
+ # @param [#pos=, #read] io The streaming object.
45
+ # @return [ELFTools::Structs::ELFStruct] Itself.
46
+ def read(io)
47
+ start = io.pos if io.respond_to?(:pos)
48
+ super.tap { @source = start.nil? ? to_binary_s : bytes_read(io, start) }
49
+ end
50
+
51
+ # Which bytes of this structure have been changed since it was read.
52
+ #
53
+ # Every field answers alike, however deeply it is nested, because what is
54
+ # compared is the bytes the structure occupies rather than the
55
+ # assignments that were made to it. A field assigned the value it
56
+ # already held leaves nothing behind.
57
+ # @return [Hash{Integer => String}]
58
+ # Where each run of changed bytes starts, as an offset into the
59
+ # structure, and the bytes it is to be replaced with.
60
+ # @example
61
+ # header.e_ident.ei_abiversion = 41
62
+ # header.patches
63
+ # #=> { 8 => "\x29" }
23
64
  def patches
24
- @patches ||= {}
65
+ return {} if @source.nil?
66
+
67
+ changed_runs(@source, to_binary_s)
25
68
  end
26
69
 
27
70
  # BinData hash(Snapshot) that behaves like HashWithIndifferentAccess
28
71
  alias to_h snapshot
29
72
 
73
+ private
74
+
75
+ # The bytes a read has just taken from a stream, leaving it where the
76
+ # read left it.
77
+ # @param [#pos=, #read] io The streaming object.
78
+ # @param [Integer] start Where the read began.
79
+ # @return [String] The bytes.
80
+ def bytes_read(io, start)
81
+ here = io.pos
82
+ io.pos = start
83
+ io.read(here - start).tap { io.pos = here }
84
+ end
85
+
86
+ # Where two strings of bytes differ, as the runs of bytes that differ.
87
+ #
88
+ # Only as far as +before+ reaches, so that a patch never covers more of
89
+ # the file than the structure it came from.
90
+ # @param [String] before The bytes as they were.
91
+ # @param [String] after The bytes as they are.
92
+ # @return [Hash{Integer => String}] Where each run starts, and its bytes.
93
+ def changed_runs(before, after)
94
+ runs = {}
95
+ start = nil
96
+ (0..before.bytesize).each do |i|
97
+ if i < before.bytesize && before.getbyte(i) != after.getbyte(i)
98
+ start ||= i
99
+ elsif start
100
+ runs[start] = after.byteslice(start, i - start)
101
+ start = nil
102
+ end
103
+ end
104
+ runs
105
+ end
106
+
30
107
  class << self
31
108
  # Hooks the constructor.
32
109
  #
@@ -41,28 +118,60 @@ module ELFTools
41
118
  def new(*args)
42
119
  kwargs = args.last.is_a?(Hash) ? args.last : {}
43
120
  offset = kwargs.delete(:offset)
44
- super.tap do |obj|
45
- obj.offset = offset
46
- obj.field_names.each do |f|
47
- m = :"#{f}="
48
- old_method = obj.singleton_method(m)
49
- obj.singleton_class.send(:undef_method, m)
50
- obj.define_singleton_method(m) do |val|
51
- org = obj.send(f)
52
- obj.patches[org.abs_offset] = ELFStruct.pack(val, org.num_bytes)
53
- old_method.call(val)
54
- end
55
- end
56
- end
121
+ super.tap { |obj| obj.offset = offset }
57
122
  end
58
123
 
59
124
  # Gets the endianness of current class.
125
+ #
126
+ # A class is of one endianness for as long as it exists, and asking
127
+ # bindata what it is named costs more than remembering the answer.
60
128
  # @return [:little, :big] The endianness.
61
129
  def self_endian
62
- bindata_name[-2..] == 'be' ? :big : :little
130
+ @self_endian ||= bindata_name[-2..] == 'be' ? :big : :little
131
+ end
132
+
133
+ # What the fields of a structure record, read straight from its bytes.
134
+ #
135
+ # Reading a table of structures costs a structure for every entry of
136
+ # it otherwise, which is most of what reading the table costs. Nothing
137
+ # is remembered of the bytes, so a caller that means to assign to a
138
+ # field wants a structure instead.
139
+ # @param [String] bytes The bytes a structure is recorded in.
140
+ # @param [Integer] elf_class 32 or 64, which decides how wide the fields recording an address are.
141
+ # @param [:little, :big] endian The endianness the file records it in.
142
+ # @return [Hash{Symbol => Integer}] Each field, and what it records.
143
+ # @raise [ELFTools::ELFError] If this is not a structure of integers.
144
+ # @example
145
+ # ELF64_sym.unpack_fields(bytes, elf_class: 64, endian: :little)
146
+ # #=> { st_name: 1, st_info: 18, st_other: 0, st_shndx: 15, st_value: 4198864, st_size: 101 }
147
+ def unpack_fields(bytes, elf_class:, endian:)
148
+ values = bytes.unpack(unpack_template(elf_class, endian))
149
+ fields = {}
150
+ # Paired by hand rather than zipped, which would make an array for
151
+ # every field of every structure read.
152
+ field_names(elf_class, endian).each_with_index { |name, i| fields[name] = values[i] }
153
+ fields
154
+ end
155
+
156
+ # How many bytes a structure of this kind takes.
157
+ # @param [Integer] elf_class 32 or 64, which decides how wide the fields recording an address are.
158
+ # @param [:little, :big] endian The endianness the file records it in.
159
+ # @return [Integer] The number.
160
+ # @example
161
+ # ELF64_sym.num_bytes(elf_class: 64, endian: :little)
162
+ # #=> 24
163
+ def num_bytes(elf_class:, endian:)
164
+ @num_bytes ||= {}
165
+ # Nested rather than keyed by the pair, which would make an array of
166
+ # it for every structure read.
167
+ (@num_bytes[elf_class] ||= {})[endian] ||= prototype(elf_class, endian).num_bytes
63
168
  end
64
169
 
65
170
  # Packs an integer to string.
171
+ #
172
+ # @deprecated
173
+ # Nothing here packs a patch by hand anymore, see {ELFStruct#patches}.
174
+ # This is kept for anyone who called it and goes in the next major.
66
175
  # @param [Integer] val
67
176
  # @param [Integer] bytes
68
177
  # @return [String]
@@ -78,6 +187,210 @@ module ELFTools
78
187
  out = out.pack('C*')
79
188
  self_endian == :little ? out : out.reverse
80
189
  end
190
+
191
+ private
192
+
193
+ # A structure of this kind with every bit of it set, which the layout is
194
+ # read off: how wide each field is, and whether it records a sign.
195
+ #
196
+ # Every structure of a kind and a class is laid out alike, so one is
197
+ # built for the kind rather than for each question asked about it.
198
+ # @param [Integer] elf_class 32 or 64, which decides how wide the fields recording an address are.
199
+ # @param [:little, :big] endian The endianness the file records it in.
200
+ # @return [ELFTools::Structs::ELFStruct] The structure.
201
+ def prototype(elf_class, endian)
202
+ @prototypes ||= {}
203
+ (@prototypes[elf_class] ||= {})[endian] ||= begin
204
+ struct = new(endian: endian)
205
+ struct.elf_class = elf_class
206
+ struct.read(SET_BITS)
207
+ end
208
+ end
209
+
210
+ # What the fields of this structure are named, in the order they are
211
+ # recorded.
212
+ # @param [Integer] elf_class 32 or 64, which decides how wide the fields recording an address are.
213
+ # @param [:little, :big] endian The endianness the file records it in.
214
+ # @return [Array<Symbol>] The names.
215
+ def field_names(elf_class, endian)
216
+ @field_names ||= {}
217
+ (@field_names[elf_class] ||= {})[endian] ||= prototype(elf_class, endian).field_names
218
+ end
219
+
220
+ # How the fields of this structure are packed, as a template for
221
+ # +String#unpack+.
222
+ # @param [Integer] elf_class 32 or 64, which decides how wide the fields recording an address are.
223
+ # @param [:little, :big] endian The endianness the file records it in.
224
+ # @return [String] The template.
225
+ def unpack_template(elf_class, endian)
226
+ @unpack_templates ||= {}
227
+ (@unpack_templates[elf_class] ||= {})[endian] ||=
228
+ prototype(elf_class, endian).each_pair.map { |_, field| field_template(field, endian) }.join
229
+ end
230
+
231
+ # How one field is packed, which is its width and whether what it read
232
+ # from a field of set bits came back negative.
233
+ # @param [BinData::Base] field The field, read from bits that are all set.
234
+ # @param [:little, :big] endian The endianness the file records it in.
235
+ # @return [String] The template.
236
+ # @raise [ELFTools::ELFError] If the field is not an integer of 1, 2, 4, or 8 bytes.
237
+ def field_template(field, endian)
238
+ width = field.num_bytes
239
+ template = UNPACK_TEMPLATES.fetch(endian)
240
+ raise ELFError, format('%s is not a structure of integers', name) unless template[false].key?(width)
241
+
242
+ template.fetch(field.to_i.negative?)[width]
243
+ end
244
+ end
245
+ end
246
+
247
+ # What a structure the file records at an offset holds, read without
248
+ # building the structure.
249
+ #
250
+ # Reading a table of structures costs a structure for every entry of it
251
+ # otherwise, and setting up the fields of one is most of what it costs.
252
+ # {#struct} builds one for whatever wants the structure itself, which
253
+ # is what assigning to a field takes, and what is assigned is answered
254
+ # with from then on.
255
+ class Fields
256
+ attr_reader :elf_class # @return [Integer] 32 or 64.
257
+ attr_reader :endian # @return [:little, :big] The endianness the file records it in.
258
+
259
+ # What a structure already built holds, for a caller that has one.
260
+ # @param [ELFTools::Structs::ELFStruct] struct The structure.
261
+ # @return [ELFTools::Structs::Fields] The fields.
262
+ def self.of(struct)
263
+ allocate.tap { |fields| fields.send(:built_from, struct) }
264
+ end
265
+
266
+ # Fields nothing in the file records, for what a file states some other
267
+ # way than by recording a structure of it.
268
+ #
269
+ # There is nothing to read and nothing to patch, so a structure is only
270
+ # built if something asks for one, and is built from these.
271
+ # @param [Class] klass The structure class.
272
+ # @param [Hash{Symbol => Integer}] fields What each field is to record.
273
+ # @param [Integer] elf_class 32 or 64.
274
+ # @param [:little, :big] endian The endianness the file records it in.
275
+ # @param [Integer] offset Where in the file this came from.
276
+ # @return [ELFTools::Structs::Fields] The fields.
277
+ # @example
278
+ # Fields.from(ELF_Rel, { r_offset: 0x1000 }, elf_class: 64, endian: :little, offset: 0x40)
279
+ def self.from(klass, fields, elf_class:, endian:, offset:)
280
+ allocate.tap { |made| made.send(:made_of, klass, fields, elf_class, endian, offset) }
281
+ end
282
+
283
+ # @param [Class] klass The structure class.
284
+ # @param [#pos=, #read] stream The streaming object.
285
+ # @param [Integer] offset The file offset the structure is recorded at.
286
+ # @param [Integer] elf_class 32 or 64.
287
+ # @param [:little, :big] endian The endianness the file records it in.
288
+ # @raise [EOFError] If the file does not reach that far.
289
+ def initialize(klass, stream, offset, elf_class:, endian:)
290
+ @klass = klass
291
+ @stream = stream
292
+ @offset = offset
293
+ @elf_class = elf_class
294
+ @endian = endian
295
+ @fields = unpack
296
+ end
297
+
298
+ # What one field of the structure records.
299
+ #
300
+ # The structure answers once there is one, so that a field assigned to
301
+ # reads back as it was assigned.
302
+ # @param [Symbol] name The name of the field.
303
+ # @return [Integer] The value.
304
+ # @example
305
+ # fields[:st_value]
306
+ # #=> 4198864
307
+ def [](name)
308
+ return @struct[name].to_i if @struct
309
+
310
+ @fields[name]
311
+ end
312
+
313
+ # Assigns to one field.
314
+ #
315
+ # The structure takes the assignment wherever the file records one, so
316
+ # that {ELFTools::ELFFile#patches} reports it. Fields the file records
317
+ # no structure of take it themselves, there being nothing to patch.
318
+ # @param [Symbol] name The name of the field.
319
+ # @param [Integer] value What it is to record.
320
+ # @return [void]
321
+ def []=(name, value)
322
+ if @struct.nil? && @stream.nil?
323
+ @fields[name] = value
324
+ else
325
+ struct[name] = value
326
+ end
327
+ end
328
+
329
+ # The structure itself, read from where the file records it, or built
330
+ # from these fields where it records none.
331
+ #
332
+ # The same one however often it is asked for, so that assigning to a
333
+ # field of it is not forgotten.
334
+ # @return [ELFTools::Structs::ELFStruct] The structure.
335
+ def struct
336
+ @struct ||= @stream.nil? ? build : read
337
+ end
338
+
339
+ private
340
+
341
+ # Takes a structure that is already built as what these fields hold.
342
+ # @param [ELFTools::Structs::ELFStruct] struct The structure.
343
+ # @return [void]
344
+ def built_from(struct)
345
+ @struct = struct
346
+ @elf_class = struct.elf_class
347
+ @endian = struct.class.self_endian
348
+ end
349
+
350
+ # Takes fields nothing in the file records a structure of.
351
+ # @return [void]
352
+ def made_of(klass, fields, elf_class, endian, offset)
353
+ @klass = klass
354
+ @fields = fields
355
+ @elf_class = elf_class
356
+ @endian = endian
357
+ @offset = offset
358
+ end
359
+
360
+ # The structure the file records, read from where it records it.
361
+ # @return [ELFTools::Structs::ELFStruct] The structure.
362
+ def read
363
+ struct = new_struct
364
+ @stream.pos = @offset
365
+ struct.read(@stream)
366
+ end
367
+
368
+ # A structure holding what these fields hold, for fields the file
369
+ # records no structure of.
370
+ # @return [ELFTools::Structs::ELFStruct] The structure.
371
+ def build
372
+ new_struct.tap { |struct| @fields.each { |name, value| struct[name] = value } }
373
+ end
374
+
375
+ # A structure of this kind, of the class and the order of the file, and
376
+ # of where in it this came from.
377
+ # @return [ELFTools::Structs::ELFStruct] The structure.
378
+ def new_struct
379
+ struct = @klass.new(endian: @endian, offset: @offset)
380
+ struct.elf_class = @elf_class
381
+ struct
382
+ end
383
+
384
+ # What the fields record, read from the bytes recording them.
385
+ # @return [Hash{Symbol => Integer}] Each field, and what it records.
386
+ # @raise [EOFError] If the file does not reach that far, as reading the structure itself does.
387
+ def unpack
388
+ num_bytes = @klass.num_bytes(elf_class: @elf_class, endian: @endian)
389
+ @stream.pos = @offset
390
+ bytes = @stream.read(num_bytes)
391
+ raise EOFError, 'End of file reached' if bytes.nil? || bytes.bytesize < num_bytes
392
+
393
+ @klass.unpack_fields(bytes, elf_class: @elf_class, endian: @endian)
81
394
  end
82
395
  end
83
396
 
@@ -200,6 +513,49 @@ module ELFTools
200
513
  uint32 :shift2 # The second shift the bloom filter is built with
201
514
  end
202
515
 
516
+ # An entry of the table of versions a file needs of another, which the
517
+ # +vn_next+ of the one before it points at.
518
+ class ELF_Verneed < ELFStruct
519
+ endian :big_and_little
520
+ uint16 :vn_version # Revision of this structure
521
+ uint16 :vn_cnt # How many versions of the file are needed
522
+ uint32 :vn_file # Name of the file, as an offset into the string table
523
+ uint32 :vn_aux # Where the versions start, as an offset from here
524
+ uint32 :vn_next # Where the next entry is, as an offset from here
525
+ end
526
+
527
+ # A version an {ELF_Verneed} needs, which the +vna_next+ of the one before
528
+ # it points at.
529
+ class ELF_Vernaux < ELFStruct
530
+ endian :big_and_little
531
+ uint32 :vna_hash # Hash of the name
532
+ uint16 :vna_flags # Flags
533
+ uint16 :vna_other # The index the symbols name this version with
534
+ uint32 :vna_name # The name, as an offset into the string table
535
+ uint32 :vna_next # Where the next version is, as an offset from here
536
+ end
537
+
538
+ # An entry of the table of versions a file defines, which the +vd_next+ of
539
+ # the one before it points at.
540
+ class ELF_Verdef < ELFStruct
541
+ endian :big_and_little
542
+ uint16 :vd_version # Revision of this structure
543
+ uint16 :vd_flags # Flags
544
+ uint16 :vd_ndx # The index the symbols name this version with
545
+ uint16 :vd_cnt # How many names follow, the version and its ancestors
546
+ uint32 :vd_hash # Hash of the name
547
+ uint32 :vd_aux # Where the names start, as an offset from here
548
+ uint32 :vd_next # Where the next entry is, as an offset from here
549
+ end
550
+
551
+ # A name an {ELF_Verdef} records, its own or an ancestor's, which the
552
+ # +vda_next+ of the one before it points at.
553
+ class ELF_Verdaux < ELFStruct
554
+ endian :big_and_little
555
+ uint32 :vda_name # The name, as an offset into the string table
556
+ uint32 :vda_next # Where the next name is, as an offset from here
557
+ end
558
+
203
559
  # Note header.
204
560
  class ELF_Nhdr < ELFStruct
205
561
  endian :big_and_little
data/lib/elftools/util.rb CHANGED
@@ -3,6 +3,11 @@
3
3
  module ELFTools
4
4
  # Define some util methods.
5
5
  module Util
6
+ # How many bytes {ClassMethods#cstring} takes from a stream at a time. Long
7
+ # enough that a name is usually read in one, short enough that reading one
8
+ # never reaches far past its end.
9
+ CSTRING_CHUNK = 64
10
+
6
11
  # Class methods.
7
12
  module ClassMethods
8
13
  # Round up the number to be multiple of
@@ -23,6 +28,26 @@ module ELFTools
23
28
  (num + n) & ~(n - 1)
24
29
  end
25
30
 
31
+ # Checks a value is one the bits recording it can hold.
32
+ #
33
+ # Several of the values a file records share a byte with others, so a
34
+ # value too large for its bits would be written over its neighbours
35
+ # instead of being rejected.
36
+ # @param [Integer] value The value.
37
+ # @param [Integer] bits How many bits record it.
38
+ # @param [String] name What the value is, for the error to name.
39
+ # @return [Integer] The value.
40
+ # @raise [ArgumentError] If the bits cannot hold it.
41
+ # @example
42
+ # Util.fits!(16, 4, 'Symbol binding')
43
+ # #=> ArgumentError: Symbol binding must be in 0..15, got 16
44
+ def fits!(value, bits, name)
45
+ highest = (1 << bits) - 1
46
+ return value if value.is_a?(Integer) && value.between?(0, highest)
47
+
48
+ raise ArgumentError, format('%s must be in 0..%d, got %p', name, highest, value)
49
+ end
50
+
26
51
  # Fetch the correct value from module +mod+.
27
52
  #
28
53
  # See {ELFTools::ELFFile#segment_by_type} for how to
@@ -38,20 +63,22 @@ module ELFTools
38
63
  module_name = mod.name.sub('ELFTools::', '')
39
64
  # if val is an integer, check if exists in mod
40
65
  if val.is_a?(Integer)
41
- return val if mod.constants.any? { |c| mod.const_get(c) == val }
66
+ return val if values_of(mod).key?(val)
42
67
 
43
68
  raise ArgumentError, "No constants in #{module_name} is #{val}"
44
69
  end
45
70
  val = val.to_s.upcase
46
71
  prefix = module_name.split('::')[-1]
47
72
  val = "#{prefix}_#{val}" unless val.start_with?(prefix)
48
- val = val.to_sym
49
- raise ArgumentError, "No constants in #{module_name} named \"#{val}\"" unless mod.const_defined?(val)
73
+ value = constants_of(mod)[val]
74
+ raise ArgumentError, "No constants in #{module_name} named \"#{val}\"" if value.nil?
50
75
 
51
- mod.const_get(val)
76
+ value
52
77
  end
53
78
 
54
79
  # Read from stream until reach a null-byte.
80
+ #
81
+ # The stream is left just past the null-byte.
55
82
  # @param [#pos=, #read] stream Streaming object.
56
83
  # @param [Integer] offset Start from here.
57
84
  # @return [String] Result string will never contain null byte.
@@ -60,16 +87,19 @@ module ELFTools
60
87
  # #=> "\x7FELF\x02\x01\x01"
61
88
  def cstring(stream, offset)
62
89
  stream.pos = offset
63
- # read until "\x00"
64
- ret = ''
90
+ ret = +''
65
91
  loop do
66
- c = stream.read(1)
67
- return nil if c.nil? # reach EOF
68
- break if c == "\x00"
92
+ chunk = stream.read(CSTRING_CHUNK)
93
+ return nil if chunk.nil? # reach EOF
94
+
95
+ stop = chunk.index("\x00")
96
+ if stop
97
+ stream.pos = offset + ret.bytesize + stop + 1
98
+ return ret << chunk.byteslice(0, stop)
99
+ end
69
100
 
70
- ret += c
101
+ ret << chunk
71
102
  end
72
- ret
73
103
  end
74
104
 
75
105
  # Select objects from enumerator with +.type+ property
@@ -93,6 +123,33 @@ module ELFTools
93
123
  end
94
124
  end
95
125
  end
126
+
127
+ private
128
+
129
+ # What a module names each of its constants, and what each is worth.
130
+ #
131
+ # Names are compared upcased, which is how a constant keeping the case
132
+ # the ABI wrote it in, +SHT_GNU_verneed+ for one, is found by the name it
133
+ # is asked for. Read once per module, because reading them is more work
134
+ # than the lookup it is for.
135
+ # @param [Module] mod The module.
136
+ # @return [Hash{String => Integer}] The constants, by their upcased name.
137
+ def constants_of(mod)
138
+ (@constants_of ||= {})[mod] ||= mod.constants.each_with_object({}) do |constant, table|
139
+ # A constant still waiting to be autoloaded is left alone, so that
140
+ # asking after one never loads what nothing has asked for.
141
+ next if mod.autoload?(constant)
142
+
143
+ table[constant.to_s.upcase] ||= mod.const_get(constant)
144
+ end
145
+ end
146
+
147
+ # Which values a module names a constant for.
148
+ # @param [Module] mod The module.
149
+ # @return [Hash{Integer => Boolean}] The values, as the keys.
150
+ def values_of(mod)
151
+ (@values_of ||= {})[mod] ||= constants_of(mod).values.to_h { |value| [value, true] }
152
+ end
96
153
  end
97
154
  extend ClassMethods
98
155
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module ELFTools
4
4
  # Current gem version
5
- VERSION = '2.0.0'
5
+ VERSION = '2.2.0'
6
6
  end