Class: Rubycc::ObjFile::ArWriter

Inherits:
Object
  • Object
show all
Defined in:
lib/rubycc/objfile/ar_archive.rb

Overview

Builds a GNU-format ar archive. The counterpart of ArReader, mirroring ELFWriter's builder taste: members are appended in order with #add_member and the whole image is assembled by #to_binary.

Every archive is written with a classic ranlib symbol index as its first / member (always, even when empty), so output is a drop-in for ar rcs and the linker can extract lazily. The index's symbols are gathered by handing each member's bytes to ELFReader and taking its defined global and weak symbols; a member ELFReader cannot parse (a non-ELF file) simply contributes none. Long member names (over 15 characters, once the trailing / is counted) move to a // extended-name table and are referenced by a /N byte offset; shorter names are stored inline as name/.

Output is fully deterministic (N4): mtime, uid and gid are pinned to 0 and the mode to 0644 (the metadata GNU ar writes in deterministic mode), so identical member bytes always produce a byte-identical archive.

Constant Summary collapse

DETERMINISTIC_MTIME =

Pinned deterministic member metadata. The reserved / and // members carry a 0 mode, matching GNU.

"0"
DETERMINISTIC_UID =
"0"
DETERMINISTIC_GID =
"0"
REGULAR_MODE =
"644"
SPECIAL_MODE =
"0"
INLINE_NAME_CAPACITY =

A name plus its trailing-slash form fits inline when at most this many bytes; a longer name goes to the // table.

16

Instance Method Summary collapse

Constructor Details

#initializeArWriter

Returns a new instance of ArWriter.



287
288
289
# File 'lib/rubycc/objfile/ar_archive.rb', line 287

def initialize
  @members = []
end

Instance Method Details

#add_member(name, data) ⇒ Object

Appends a member with the given name and raw data. Names are not de-duplicated here — replace-or-append is the CLI's job; the writer lays out exactly what it is given, in order.



294
295
296
297
# File 'lib/rubycc/objfile/ar_archive.rb', line 294

def add_member(name, data)
  @members << { name: name.to_s, data: data.b }
  self
end

#to_binaryObject

Assembles and returns the archive as an ASCII-8BIT String.



300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'lib/rubycc/objfile/ar_archive.rb', line 300

def to_binary
  entries = @members.map { |m| { name: m[:name], data: m[:data], symbols: gather_symbols(m[:data]) } }

  name_table, name_field = build_name_table(entries)
  flat_symbols = flatten_symbols(entries)
  symtab_size = symtab_byte_size(flat_symbols)

  # The symbol index and the name table precede every regular member, so
  # each regular member's header offset — the value the index records — is
  # known once their spans are summed. The index's own size depends only on
  # the symbol count and names, not on the offsets it will hold, so there is
  # no circular dependency.
  base = AR_MAGIC.bytesize + member_span(symtab_size)
  base += member_span(name_table.bytesize) unless name_table.empty?
  offset = base
  entries.each do |entry|
    entry[:header_offset] = offset
    offset += member_span(entry[:data].bytesize)
  end

  symtab = build_symtab(flat_symbols, entries)

  out = +"".b
  out << AR_MAGIC
  emit_member(out, "/", symtab, SPECIAL_MODE)
  emit_member(out, "//", name_table, SPECIAL_MODE) unless name_table.empty?
  entries.each { |entry| emit_member(out, name_field[entry.object_id], entry[:data], REGULAR_MODE) }
  out
end

#write(path) ⇒ Object

Convenience: assemble and write the archive to path.



331
332
333
# File 'lib/rubycc/objfile/ar_archive.rb', line 331

def write(path)
  File.binwrite(path, to_binary)
end