# This file is part of Metasm, the Ruby assembly manipulation suite # Copyright (C) 2006-2009 Yoann GUILLOT # # Licence is LGPL, see LICENCE in the top-level directory require 'metasm/encode' require 'metasm/exe_format/coff' unless defined? Metasm::COFF module Metasm class COFF class OptionalHeader # encodes an Optional header and the directories def encode(coff) opth = super(coff) DIRECTORIES[0, @numrva].each { |d| if d = coff.directory[d] d = d.dup d[0] = Expression[d[0], :-, coff.label_at(coff.encoded, 0)] if d[0].kind_of?(::String) else d = [0, 0] end opth << coff.encode_word(d[0]) << coff.encode_word(d[1]) } opth end # find good default values for optheader members, based on coff.sections def set_default_values(coff) @signature ||= (coff.bitsize == 64 ? 'PE+' : 'PE') @link_ver_maj ||= 1 @link_ver_min ||= 0 @sect_align ||= 0x1000 align = lambda { |sz| EncodedData.align_size(sz, @sect_align) } @code_size ||= coff.sections.find_all { |s| s.characteristics.include? 'CONTAINS_CODE' }.inject(0) { |sum, s| sum + align[s.virtsize] } @data_size ||= coff.sections.find_all { |s| s.characteristics.include? 'CONTAINS_DATA' }.inject(0) { |sum, s| sum + align[s.virtsize] } @udata_size ||= coff.sections.find_all { |s| s.characteristics.include? 'CONTAINS_UDATA' }.inject(0) { |sum, s| sum + align[s.virtsize] } @entrypoint = Expression[@entrypoint, :-, coff.label_at(coff.encoded, 0)] if entrypoint and not @entrypoint.kind_of?(::Integer) tmp = coff.sections.find { |s| s.characteristics.include? 'CONTAINS_CODE' } @base_of_code ||= (tmp ? Expression[coff.label_at(tmp.encoded, 0), :-, coff.label_at(coff.encoded, 0)] : 0) tmp = coff.sections.find { |s| s.characteristics.include? 'CONTAINS_DATA' } @base_of_data ||= (tmp ? Expression[coff.label_at(tmp.encoded, 0), :-, coff.label_at(coff.encoded, 0)] : 0) @file_align ||= 0x200 @os_ver_maj ||= 4 @subsys_maj ||= 4 @stack_reserve||= 0x100000 @stack_commit ||= 0x1000 @heap_reserve ||= 0x100000 @heap_commit ||= 0x1000 @numrva ||= DIRECTORIES.length super(coff) end end class Section # find good default values for section header members, defines rawaddr/rawsize as new_label for later fixup def set_default_values(coff) @name ||= '' @virtsize ||= @encoded.virtsize @virtaddr ||= Expression[coff.label_at(@encoded, 0, 'sect_start'), :-, coff.label_at(coff.encoded, 0)] @rawsize ||= coff.new_label('sect_rawsize') @rawaddr ||= coff.new_label('sect_rawaddr') super(coff) end end class ExportDirectory # encodes an export directory def encode(coff) edata = {} %w[edata addrtable namptable ord_table libname nametable].each { |name| edata[name] = EncodedData.new } label = lambda { |n| coff.label_at(edata[n], 0, n) } rva = lambda { |n| Expression[label[n], :-, coff.label_at(coff.encoded, 0)] } rva_end = lambda { |n| Expression[[label[n], :-, coff.label_at(coff.encoded, 0)], :+, edata[n].virtsize] } # ordinal base: smallest number > 1 to honor ordinals, minimize gaps olist = @exports.map { |e| e.ordinal }.compact # start with lowest ordinal, substract all exports unused to fill ordinal sequence gaps omin = olist.min.to_i gaps = olist.empty? ? 0 : olist.max+1 - olist.min - olist.length noord = @exports.length - olist.length @ordinal_base ||= [omin - (noord - gaps), 1].max @libname_p = rva['libname'] @num_exports = [@exports.length, @exports.map { |e| e.ordinal }.compact.max.to_i - @ordinal_base].max @num_names = @exports.find_all { |e| e.name }.length @func_p = rva['addrtable'] @names_p = rva['namptable'] @ord_p = rva['ord_table'] edata['edata'] << super(coff) edata['libname'] << @libname << 0 elist = @exports.find_all { |e| e.name and not e.ordinal }.sort_by { |e| e.name } @exports.find_all { |e| e.ordinal }.sort_by { |e| e.ordinal }.each { |e| elist.insert(e.ordinal-@ordinal_base, e) } elist.each { |e| if not e # export by ordinal with gaps # XXX test this value with the windows loader edata['addrtable'] << coff.encode_word(0xffff_ffff) next end if e.forwarder_lib edata['addrtable'] << coff.encode_word(rva_end['nametable']) edata['nametable'] << e.forwarder_lib << ?. << if not e.forwarder_name "##{e.forwarder_ordinal}" else e.forwarder_name end << 0 else edata['addrtable'] << coff.encode_word(Expression[e.target, :-, coff.label_at(coff.encoded, 0)]) end if e.name edata['ord_table'] << coff.encode_half(edata['addrtable'].virtsize/4 - 1) edata['namptable'] << coff.encode_word(rva_end['nametable']) edata['nametable'] << e.name << 0 end } # sorted by alignment directives %w[edata addrtable namptable ord_table libname nametable].inject(EncodedData.new) { |ed, name| ed << edata[name] } end def set_default_values(coff) @timestamp ||= Time.now.to_i @libname ||= 'metalib' @ordinal_base ||= 1 super(coff) end end class ImportDirectory # encodes all import directories + iat def self.encode(coff, ary) edata = { 'iat' => [] } %w[idata ilt nametable].each { |name| edata[name] = EncodedData.new } ary.each { |i| i.encode(coff, edata) } it = edata['idata'] << coff.encode_word(0) << coff.encode_word(0) << coff.encode_word(0) << coff.encode_word(0) << coff.encode_word(0) << edata['ilt'] << edata['nametable'] iat = edata['iat'] # why not fragmented ? [it, iat] end # encodes an import directory + iat + names in the edata hash received as arg def encode(coff, edata) edata['iat'] << EncodedData.new # edata['ilt'] = edata['iat'] label = lambda { |n| coff.label_at(edata[n], 0, n) } rva = lambda { |n| Expression[label[n], :-, coff.label_at(coff.encoded, 0)] } rva_end = lambda { |n| Expression[[label[n], :-, coff.label_at(coff.encoded, 0)], :+, edata[n].virtsize] } @libname_p = rva_end['nametable'] @ilt_p = rva_end['ilt'] @iat_p ||= Expression[coff.label_at(edata['iat'].last, 0, 'iat'), :-, coff.label_at(coff.encoded, 0)] edata['idata'] << super(coff) edata['nametable'] << @libname << 0 ord_mask = 1 << (coff.bitsize - 1) @imports.each { |i| edata['iat'].last.add_export i.target, edata['iat'].last.virtsize if i.target if i.ordinal ptr = coff.encode_xword(Expression[i.ordinal, :|, ord_mask]) else edata['nametable'].align 2 ptr = coff.encode_xword(rva_end['nametable']) edata['nametable'] << coff.encode_half(i.hint || 0) << i.name << 0 end edata['ilt'] << ptr edata['iat'].last << ptr } edata['ilt'] << coff.encode_xword(0) edata['iat'].last << coff.encode_xword(0) end end class TLSDirectory def encode(coff) cblist = EncodedData.new @callback_p = coff.label_at(cblist, 0, 'callback_p') @callbacks.to_a.each { |cb| cblist << coff.encode_xword(cb) } cblist << coff.encode_xword(0) dir = super(coff) [dir, cblist] end def set_default_values(coff) @start_va ||= 0 @end_va ||= @start_va super(coff) end end class RelocationTable # encodes a COFF relocation table def encode(coff) rel = super(coff) << coff.encode_word(8 + 2*@relocs.length) @relocs.each { |r| rel << r.encode(coff) } rel end def set_default_values(coff) # @base_addr is an rva @base_addr = Expression[@base_addr, :-, coff.label_at(coff.encoded, 0)] if @base_addr.kind_of?(::String) # align relocation table size if @relocs.length % 2 != 0 r = Relocation.new r.type = 0 r.offset = 0 @relocs << r end super(coff) end end class ResourceDirectory # compiles ressource directories def encode(coff, edata = nil) if not edata # init recursion edata = {} subtables = %w[table names dataentries data] subtables.each { |n| edata[n] = EncodedData.new } encode(coff, edata) return subtables.inject(EncodedData.new) { |sum, n| sum << edata[n] } end label = lambda { |n| coff.label_at(edata[n], 0, n) } # data 'rva' are real rvas (from start of COFF) rva_end = lambda { |n| Expression[[label[n], :-, coff.label_at(coff.encoded, 0)], :+, edata[n].virtsize] } # names and table 'rva' are relative to the beginning of the resource directory off_end = lambda { |n| Expression[[label[n], :-, coff.label_at(edata['table'], 0)], :+, edata[n].virtsize] } # build name_w if needed @entries.each { |e| e.name_w = e.name.unpack('C*').pack('v*') if e.name and not e.name_w } # fixup forward references to us, as subdir edata['table'].fixup @curoff_label => edata['table'].virtsize if defined? @curoff_label @nr_names = @entries.find_all { |e| e.name_w }.length @nr_id = @entries.find_all { |e| e.id }.length edata['table'] << super(coff) # encode entries, sorted by names nocase, then id @entries.sort_by { |e| e.name_w ? [0, e.name_w.downcase] : [1, e.id] }.each { |e| if e.name_w edata['table'] << coff.encode_word(Expression[off_end['names'], :|, 1 << 31]) edata['names'] << coff.encode_half(e.name_w.length/2) << e.name_w else edata['table'] << coff.encode_word(e.id) end if e.subdir e.subdir.curoff_label = coff.new_label('rsrc_curoff') edata['table'] << coff.encode_word(Expression[e.subdir.curoff_label, :|, 1 << 31]) else # data entry edata['table'] << coff.encode_word(off_end['dataentries']) edata['dataentries'] << coff.encode_word(rva_end['data']) << coff.encode_word(e.data.length) << coff.encode_word(e.codepage || 0) << coff.encode_word(e.reserved || 0) edata['data'] << e.data end } # recurse @entries.find_all { |e| e.subdir }.each { |e| e.subdir.encode(coff, edata) } end end # computes the checksum for a given COFF file # may not work with overlapping sections def self.checksum(str, endianness = :little) coff = load str coff.endianness = endianness coff.decode_header coff.encoded.ptr = 0 flen = 0 csum = 0 # negate old checksum oldcs = coff.encode_word(coff.optheader.checksum) oldcs.ptr = 0 csum -= coff.decode_half(oldcs) csum -= coff.decode_half(oldcs) # checksum header raw = coff.encoded.read(coff.optheader.headers_size) flen += coff.optheader.headers_size coff.sections.each { |s| coff.encoded.ptr = s.rawaddr raw << coff.encoded.read(s.rawsize) flen += s.rawsize } raw.unpack(endianness == :little ? 'v*' : 'n*').each { |s| csum += s csum = (csum & 0xffff) + (csum >> 16) if (csum >> 16) > 0 } csum + flen end def encode_byte(w) Expression[w].encode(:u8, @endianness, (caller if $DEBUG)) end def encode_half(w) Expression[w].encode(:u16, @endianness, (caller if $DEBUG)) end def encode_word(w) Expression[w].encode(:u32, @endianness, (caller if $DEBUG)) end def encode_xword(w) Expression[w].encode((@bitsize == 32 ? :u32 : :u64), @endianness, (caller if $DEBUG)) end # adds a new compiler-generated section def encode_append_section(s) if (s.virtsize || s.encoded.virtsize) < 4096 # find section to merge with # XXX check following sections for hardcoded base address ? char = s.characteristics.dup secs = @sections.dup # do not merge non-discardable in discardable if not char.delete 'MEM_DISCARDABLE' secs.delete_if { |ss| ss.characteristics.include? 'MEM_DISCARDABLE' } end # do not merge shared w/ non-shared if char.delete 'MEM_SHARED' secs.delete_if { |ss| not ss.characteristics.include? 'MEM_SHARED' } else secs.delete_if { |ss| ss.characteristics.include? 'MEM_SHARED' } end secs.delete_if { |ss| ss.virtsize.kind_of?(::Integer) or ss.rawsize.kind_of?(::Integer) or secs[secs.index(ss)+1..-1].find { |ss_| ss_.virtaddr.kind_of?(::Integer) } } # try to find superset of characteristics if target = secs.find { |ss| (ss.characteristics & char) == char } target.encoded.align 8 puts "PE: merging #{s.name} in #{target.name} (#{target.encoded.virtsize})" if $DEBUG s.encoded = target.encoded << s.encoded else @sections << s end else @sections << s end end # encodes the export table as a new section, updates directory['export_table'] def encode_exports edata = @export.encode self # must include name tables (for forwarders) @directory['export_table'] = [label_at(edata, 0, 'export_table'), edata.virtsize] s = Section.new s.name = '.edata' s.encoded = edata s.characteristics = %w[MEM_READ] encode_append_section s end # encodes the import tables as a new section, updates directory['import_table'] and directory['iat'] def encode_imports idata, iat = ImportDirectory.encode(self, @imports) @directory['import_table'] = [label_at(idata, 0, 'idata'), idata.virtsize] s = Section.new s.name = '.idata' s.encoded = idata s.characteristics = %w[MEM_READ MEM_WRITE MEM_DISCARDABLE] encode_append_section s if @imports.first and @imports.first.iat_p.kind_of? Integer ordiat = @imports.zip(iat).sort_by { |id, it| id.iat_p.kind_of?(Integer) ? id.iat_p : 1<<65 }.map { |id, it| it } else ordiat = iat end @directory['iat'] = [label_at(ordiat.first, 0, 'iat'), Expression[label_at(ordiat.last, ordiat.last.virtsize, 'iat_end'), :-, label_at(ordiat.first, 0)]] if not ordiat.empty? iat_s = nil plt = Section.new plt.name = '.plt' plt.encoded = EncodedData.new plt.characteristics = %w[MEM_READ MEM_EXECUTE] @imports.zip(iat) { |id, it| if id.iat_p.kind_of? Integer and s = @sections.find { |s_| s_.virtaddr <= id.iat_p and s_.virtaddr + (s_.virtsize || s_.encoded.virtsize) > id.iat_p } id.iat = it # will be fixed up after encode_section else # XXX should not be mixed (for @directory['iat'][1]) if not iat_s iat_s = Section.new iat_s.name = '.iat' iat_s.encoded = EncodedData.new iat_s.characteristics = %w[MEM_READ MEM_WRITE] encode_append_section iat_s end iat_s.encoded << it end id.imports.each { |i| if i.thunk arch_encode_thunk(plt.encoded, i) end } } encode_append_section plt if not plt.encoded.empty? end # encodes a thunk to imported function def arch_encode_thunk(edata, import) case @cpu.shortname when 'ia32', 'x64' shellcode = lambda { |c| Shellcode.new(@cpu).share_namespace(self).assemble(c).encoded } if @cpu.generate_PIC if @cpu.shortname == 'x64' edata << shellcode["#{import.thunk}: jmp [rip-$_+#{import.target}]"] return end # sections starts with a helper function that returns the address of metasm_intern_geteip in eax (PIC) if not @sections.find { |s| s.encoded and s.encoded.export['metasm_intern_geteip'] } and edata.empty? edata << shellcode["metasm_intern_geteip: call 42f\n42:\npop eax\nsub eax, 42b-metasm_intern_geteip\nret"] end edata << shellcode["#{import.thunk}: call metasm_intern_geteip\njmp [eax+#{import.target}-metasm_intern_geteip]"] else edata << shellcode["#{import.thunk}: jmp [#{import.target}]"] end else raise EncodeError, 'E: COFF: encode import thunk: unsupported architecture' end end def encode_tls dir, cbtable = @tls.encode(self) @directory['tls_table'] = [label_at(dir, 0, 'tls_table'), dir.virtsize] s = Section.new s.name = '.tls' s.encoded = EncodedData.new << dir << cbtable s.characteristics = %w[MEM_READ MEM_WRITE] encode_append_section s end # encodes relocation tables in a new section .reloc, updates @directory['base_relocation_table'] def encode_relocs if @relocations.empty? rt = RelocationTable.new rt.base_addr = 0 rt.relocs = [] @relocations << rt end relocs = @relocations.inject(EncodedData.new) { |edata, rt_| edata << rt_.encode(self) } @directory['base_relocation_table'] = [label_at(relocs, 0, 'reloc_table'), relocs.virtsize] s = Section.new s.name = '.reloc' s.encoded = relocs s.characteristics = %w[MEM_READ MEM_DISCARDABLE] encode_append_section s end # creates the @relocations from sections.encoded.reloc def create_relocation_tables @relocations = [] # create a fake binding with all exports, to find only-image_base-dependant relocs targets # not foolproof, but works in standard cases startaddr = curaddr = label_at(@encoded, 0, 'coff_start') binding = {} @sections.each { |s| binding.update s.encoded.binding(curaddr) curaddr = Expression[curaddr, :+, s.encoded.virtsize] } # for each section.encoded, make as many RelocationTables as needed @sections.each { |s| # rt.base_addr temporarily holds the offset from section_start, and is fixed up to rva before '@reloc << rt' rt = RelocationTable.new s.encoded.reloc.each { |off, rel| # check that the relocation looks like "program_start + integer" when bound using the fake binding # XXX allow :i32 etc if rel.endianness == @endianness and [:u32, :a32, :u64, :a64].include?(rel.type) and rel.target.bind(binding).reduce.kind_of?(Expression) and Expression[rel.target, :-, startaddr].bind(binding).reduce.kind_of?(::Integer) # winner ! # build relocation r = RelocationTable::Relocation.new r.offset = off & 0xfff r.type = { :u32 => 'HIGHLOW', :u64 => 'DIR64', :a32 => 'HIGHLOW', :a64 => 'DIR64' }[rel.type] # check if we need to start a new relocation table if rt.base_addr and (rt.base_addr & ~0xfff) != (off & ~0xfff) rt.base_addr = Expression[[label_at(s.encoded, 0, 'sect_start'), :-, startaddr], :+, rt.base_addr] @relocations << rt rt = RelocationTable.new end # initialize reloc table base address if needed if not rt.base_addr rt.base_addr = off & ~0xfff end (rt.relocs ||= []) << r elsif $DEBUG and not rel.target.bind(binding).reduce.kind_of?(Integer) puts "W: COFF: Ignoring weird relocation #{rel.inspect} when building relocation tables" end } if rt and rt.relocs rt.base_addr = Expression[[label_at(s.encoded, 0, 'sect_start'), :-, startaddr], :+, rt.base_addr] @relocations << rt end } end def encode_resource res = @resource.encode self @directory['resource_table'] = [label_at(res, 0, 'resource_table'), res.virtsize] s = Section.new s.name = '.rsrc' s.encoded = res s.characteristics = %w[MEM_READ] encode_append_section s end # initialize the header from target/cpu/etc, target in ['exe' 'dll' 'kmod' 'obj'] def pre_encode_header(target = 'exe', want_relocs=true) target = {:bin => 'exe', :lib => 'dll', :obj => 'obj', 'sys' => 'kmod', 'drv' => 'kmod'}.fetch(target, target) @header.machine ||= case @cpu.shortname when 'x64'; 'AMD64' when 'ia32'; 'I386' end @optheader.signature ||= case @cpu.size when 32; 'PE' when 64; 'PE+' end @bitsize = (@optheader.signature == 'PE+' ? 64 : 32) # setup header flags tmp = %w[LINE_NUMS_STRIPPED LOCAL_SYMS_STRIPPED DEBUG_STRIPPED] + case target when 'exe'; %w[EXECUTABLE_IMAGE] when 'dll'; %w[EXECUTABLE_IMAGE DLL] when 'kmod'; %w[EXECUTABLE_IMAGE] when 'obj'; [] end if @cpu.size == 32 tmp << 'x32BIT_MACHINE' else tmp << 'LARGE_ADDRESS_AWARE' end tmp << 'RELOCS_STRIPPED' if not want_relocs @header.characteristics ||= tmp @optheader.subsystem ||= case target when 'exe', 'dll'; 'WINDOWS_GUI' when 'kmod'; 'NATIVE' end tmp = [] tmp << 'NX_COMPAT' tmp << 'DYNAMIC_BASE' if want_relocs @optheader.dll_characts ||= tmp end # resets the values in the header that may have been # modified by your script (eg section count, size, imagesize, etc) # call this whenever you decode a file, modify it, and want to reencode it later def invalidate_header # set those values to nil, they will be # recomputed during encode_header [:code_size, :data_size, :udata_size, :base_of_code, :base_of_data, :sect_align, :file_align, :image_size, :headers_size, :checksum].each { |m| @optheader.send("#{m}=", nil) } [:num_sect, :ptr_sym, :num_sym, :size_opthdr].each { |m| @header.send("#{m}=", nil) } end # appends the header/optheader/directories/section table to @encoded def encode_header # encode section table, add CONTAINS_* flags from other characteristics flags s_table = EncodedData.new @sections.each { |s| if s.characteristics.kind_of? Array and s.characteristics.include? 'MEM_READ' if s.characteristics.include? 'MEM_EXECUTE' s.characteristics |= ['CONTAINS_CODE'] elsif s.encoded if s.encoded.rawsize == 0 s.characteristics |= ['CONTAINS_UDATA'] else s.characteristics |= ['CONTAINS_DATA'] end end end s.rawaddr = nil if s.rawaddr.kind_of?(::Integer) # XXX allow to force rawaddr ? s_table << s.encode(self) } # encode optional header @optheader.image_size ||= new_label('image_size') @optheader.image_base ||= label_at(@encoded, 0) @optheader.headers_size ||= new_label('headers_size') @optheader.checksum ||= new_label('checksum') @optheader.subsystem ||= 'WINDOWS_GUI' @optheader.numrva = nil opth = @optheader.encode(self) # encode header @header.machine ||= 'UNKNOWN' @header.num_sect ||= sections.length @header.time ||= Time.now.to_i & -255 @header.size_opthdr ||= opth.virtsize @encoded << @header.encode(self) << opth << s_table end # append the section bodies to @encoded, and link the resulting binary def encode_sections_fixup @encoded.align @optheader.file_align if @optheader.headers_size.kind_of?(::String) @encoded.fixup! @optheader.headers_size => @encoded.virtsize @optheader.headers_size = @encoded.virtsize end baseaddr = @optheader.image_base.kind_of?(::Integer) ? @optheader.image_base : 0x400000 binding = @encoded.binding(baseaddr) curaddr = baseaddr + @optheader.headers_size @sections.each { |s| # align curaddr = EncodedData.align_size(curaddr, @optheader.sect_align) if s.rawaddr.kind_of?(::String) @encoded.fixup! s.rawaddr => @encoded.virtsize s.rawaddr = @encoded.virtsize end if s.virtaddr.kind_of?(::Integer) raise "E: COFF: cannot encode section #{s.name}: hardcoded address too short" if curaddr > baseaddr + s.virtaddr curaddr = baseaddr + s.virtaddr end binding.update s.encoded.binding(curaddr) curaddr += s.virtsize pre_sz = @encoded.virtsize @encoded << s.encoded[0, s.encoded.rawsize] @encoded.align @optheader.file_align if s.rawsize.kind_of?(::String) @encoded.fixup! s.rawsize => (@encoded.virtsize - pre_sz) s.rawsize = @encoded.virtsize - pre_sz end } # not aligned ? spec says it is, visual studio does not binding[@optheader.image_size] = curaddr - baseaddr if @optheader.image_size.kind_of?(::String) # patch the iat where iat_p was defined # sort to ensure a 0-terminated will not overwrite an entry # (try to dump notepad.exe, which has a forwarder;) @imports.find_all { |id| id.iat_p.kind_of? Integer }.sort_by { |id| id.iat_p }.each { |id| s = sect_at_rva(id.iat_p) @encoded[s.rawaddr + s.encoded.ptr, id.iat.virtsize] = id.iat binding.update id.iat.binding(baseaddr + id.iat_p) } if imports @encoded.fill @encoded.fixup! binding if @optheader.checksum.kind_of?(::String) and @encoded.reloc.length == 1 # won't work if there are other unresolved relocs checksum = self.class.checksum(@encoded.data, @endianness) @encoded.fixup @optheader.checksum => checksum @optheader.checksum = checksum end end # encode a COFF file, building export/import/reloc tables if needed # creates the base relocation tables (need for references to IAT not known before) # defaults to generating relocatable files, eg ALSR-aware # pass want_relocs=false to avoid the file overhead induced by this def encode(target = 'exe', want_relocs = true) @encoded = EncodedData.new label_at(@encoded, 0, 'coff_start') pre_encode_header(target, want_relocs) autoimport encode_exports if export encode_imports if imports encode_resource if resource encode_tls if tls create_relocation_tables if want_relocs encode_relocs if relocations encode_header encode_sections_fixup @encoded.data end def parse_init # ahem... # a fake object, which when appended makes us parse '.text', which creates a real default section # forwards to it this first appendage. # allows the user to specify its own section if he wishes, and to use .text if he doesn't if not defined? @cursource or not @cursource @cursource = ::Object.new class << @cursource attr_accessor :coff def <<(*a) t = Preprocessor::Token.new(nil) t.raw = '.text' coff.parse_parser_instruction t coff.cursource.send(:<<, *a) end end @cursource.coff = self end @source ||= {} super() end # handles compiler meta-instructions # # syntax: # .section "
" # section name is a string (may be quoted) # perms are in 'r' 'w' 'x' 'shared' 'discard', may be concatenated (in this order), may be prefixed by 'no' to remove the attribute for an existing section # base is the token 'base', the token '=' and an immediate expression # default sections: # .text = .section '.text' rx # .data = .section '.data' rw # .rodata = .section '.rodata' r # .bss = .section '.bss' rw # .entrypoint | .entrypoint