elf-mithril 0.0.1

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.
@@ -0,0 +1,335 @@
1
+ require 'bindata'
2
+ #Nothing to see here. For background, see Dartmouth College CS
3
+ #TR2013-727 (ELFbac)
4
+ module Elf
5
+ module Policy
6
+ def self.section_symbol_name(file_name,section_name)
7
+ "_elfp_#{file_name}_#{section_name}"
8
+ end
9
+ R = ElfFlags::Relocation
10
+ ELFP = ElfFlags::ElfPData
11
+ class Transition
12
+ attr_accessor :from, :to
13
+ end
14
+ class Call < Transition
15
+ attr_accessor :symbol
16
+ attr_accessor :parambytes,:returnbytes
17
+ def initialize(from,to, symbol, parambytes, returnbytes)
18
+ @from, @to, @symbol, @parambytes, @returnbytes = from,to, symbol, parambytes, returnbytes
19
+ end
20
+ def allows_return?
21
+ @returnbytes >= 0
22
+ end
23
+ end
24
+ class Data < Transition
25
+ attr_accessor :tag
26
+ attr_accessor :read,:write, :exec
27
+ def initialize(from,to,tag , read=false ,write=false,exec=false)
28
+ @from, @to, @tag, @read,@write,@exec = from,to,tag,read,write,exec
29
+ end
30
+ end
31
+ class MemoryRange
32
+ attr_accessor :low, :high
33
+ def initialize(from,to)
34
+ @low,@high = from,to
35
+ end
36
+ end
37
+ class Policy
38
+ attr_accessor :data, :calls, :start, :tags
39
+ attr_accessor :imported_symbols
40
+ def states
41
+ t = data.keys + data.values.map(&:keys).flatten + calls.map(&:from) +
42
+ calls.map(&:to)
43
+ t.uniq
44
+ end
45
+ def <<(*transitions)
46
+ transitions.each do |t|
47
+ if t.is_a? Data
48
+ x= data
49
+ x[t.from] ||= {}
50
+ x[t.from][t.to] ||= {}
51
+ x[t.from][t.to][t.tag] ||= t
52
+ x[t.from][t.to][t.tag].read ||= t.read
53
+ x[t.from][t.to][t.tag].write ||= t.write
54
+ x[t.from][t.to][t.tag].exec ||= t.exec
55
+ elsif t.is_a? Call
56
+ calls << t
57
+ else
58
+ raise ArgumentError.new "#{t.class} is not a valid transition"
59
+ end
60
+ end
61
+ end
62
+ def initialize
63
+ @data={}
64
+ @calls=[]
65
+ @states ={}
66
+ @tags = {}
67
+ @imported_symbols = {}
68
+ end
69
+ def resolve_reference(elffile, relocations,offset, ref)
70
+ if(ref.is_a? Integer)
71
+ ref.to_i
72
+ elsif(ref == "_dl_runtime_resolve") #HACK:HACK:HACK: I couldn't hack ld.so to fix this, so
73
+ #here comes a nasty hack
74
+ #note that the address of _dl_runtime_resolve is 16 bytes into PLT.GOT
75
+ if !elffile.symbols.include? "_elfp_hidden_trampolineaddr"
76
+ pltgot = elffile.dynamic.pltgot or raise RuntimeError.new "No plt.got for _dl_runtime_resolve hack"
77
+ elffile.symbols << Elf::Symbol.new("_elfp_hidden_trampolineaddr", pltgot,STT::STT_OBJECT,16, STB::STB_LOCAL,8)
78
+ end
79
+ symb = elffile.symbols["_elfp_hidden_trampolineaddr"]
80
+ relocations << Elf::Relocation.new.tap{|x|
81
+ x.type = R::R_X86_64_COPY
82
+ x.offset = offset
83
+ x.symbol = symb
84
+ x.is_dynamic = true
85
+ x.addend = 0
86
+ }
87
+ 0xDEADBEEF
88
+ else
89
+ raise RuntimeError.new "Symbol #{ref} not found" unless elffile.symbols.include? ref
90
+ relocations << Elf::Relocation.new.tap{|x|
91
+ x.type = R::R_X86_64_64
92
+ x.offset = offset
93
+ x.symbol = elffile.symbols[ref]
94
+ x.is_dynamic = true
95
+ x.addend = 0
96
+ }
97
+ 2**64-1
98
+ end
99
+ end
100
+ def resolve_size(elffile,relocations, offset, ref)
101
+ if(ref.is_a? Integer)
102
+ ref.to_i
103
+ else
104
+ raise RuntimeError.new "Symbol #{ref} not found" unless elffile.symbols.include? ref
105
+ relocations << Elf::Relocation.new.tap{|x|
106
+ x.type = R::R_X86_64_SIZE64
107
+ x.offset = offset
108
+ x.symbol = elffile.symbols[ref]
109
+ x.is_dynamic = true
110
+ x.addend = 0
111
+ }
112
+ 2**64-1
113
+ end
114
+ end
115
+ def write_amd64(elffile)
116
+ factory = ElfStructFactory.instance(:little,64)
117
+ @imported_symbols.each_key {|symbol|
118
+ if elffile.symbols.include?(symbol)
119
+ elffile.symbols[symbol].is_dynamic = true
120
+ else
121
+ elffile.symbols << Elf::Symbol.new(symbol,nil,Elf::STT::STT_OBJECT, 0, Elf::STB::STB_GLOBAL, 0).tap {|x|
122
+ x.semantics = Elf::SHN::SHN_UNDEF
123
+ }
124
+ end
125
+ }
126
+ out = factory.elfp_header.new()
127
+ state_ids = {}
128
+ tag_ids = {}
129
+ relocations = []
130
+ states = states()
131
+ @start = states.first unless states.include? @start
132
+ #These have to be filled in the order in which they are written
133
+ #FIXME: Make these aware of double transitions to the same range/ state
134
+ states.each_with_index do |state,index|
135
+ id = index + 2
136
+ id = 1 if @start == state
137
+ out.states << factory.elfp_state.new.tap {|x|
138
+ x.id = id
139
+ x.stackid = 0
140
+ }
141
+ state_ids[state] = id
142
+ print "State #{state} #{id}\n"
143
+ end
144
+ tag_ids[:default] = 0
145
+ @tags.each_with_index do |(name,ranges),index|
146
+ tag_ids[name] = index+1
147
+ ranges.each do |data|
148
+ out.tags << factory.elfp_tag.new.tap {|x|
149
+ x.tag = index + 1
150
+ x.addr = 0
151
+ x.siz = 0
152
+ }
153
+ out.tags.last.tap {|x|
154
+ x.addr = resolve_reference(elffile,relocations,x.addr.offset,data.low)
155
+ if data.high.nil?
156
+ x.siz = resolve_size(elffile,relocations,x.siz.offset,data.low)
157
+ else
158
+ pp "Warning, emitting SIZE symbol with value #{ data.high.to_i rescue data.high.name}"
159
+ x.siz = resolve_reference(elffile,relocations,x.siz.offset,data.high)
160
+ end
161
+ }
162
+ end
163
+ print "Tag #{name} #{index + 1} \n"
164
+ end
165
+ self.calls.each do |call|
166
+ out.calls << factory.elfp_call.new.tap {|x|
167
+ x.from = state_ids[call.from]
168
+ x.to = state_ids[call.to]
169
+ x.parambytes = call.parambytes
170
+ x.returnbytes = call.parambytes
171
+ }
172
+ out.calls.last.off = resolve_reference(elffile,relocations,out.calls.last.off.offset, call.symbol)
173
+ end
174
+ self.data.values.map(&:values).flatten.map(&:values).flatten.each do |data|
175
+ out.data << factory.elfp_data.new.tap {|x|
176
+ x.from = state_ids[data.from]
177
+ x.to = state_ids[data.to]
178
+ x.type = 0
179
+ x.type |= ELFP::ELFP_RW_READ if data.read
180
+ x.type |= ELFP::ELFP_RW_WRITE if data.write
181
+ x.type |= ELFP::ELFP_RW_EXEC if data.exec
182
+ raise RuntimeError.new "Unknown tag #{data.tag}" unless tag_ids.include? data.tag
183
+ x.tag = tag_ids[data.tag]
184
+ }
185
+ end
186
+ out = Elf::ProgBits.new(".elfbac",nil,out.to_binary_s)
187
+ out.align = 8
188
+ out.flags = SHF::SHF_ALLOC | SHF::SHF_WRITE
189
+ out.sect_type = SHT::SHT_PROGBITS
190
+ out.phdr = ElfFlags::PhdrType::PT_ELFBAC
191
+ out.phdr_flags = ElfFlags::PhdrFlags::PF_R
192
+ relocations.each { |rel|
193
+ rel.section = out
194
+ elffile.relocations << rel
195
+ }
196
+ elffile.progbits << out
197
+
198
+ end
199
+ def inject(file)
200
+ case file.machine
201
+ when ElfFlags::Machine::EM_X86_64
202
+ write_amd64(file)
203
+ else
204
+ raise RuntimeError.new "Wrong architecture for ARM64"
205
+ end
206
+ end
207
+ end
208
+ module BuilderHelper
209
+ def section_start(name, file_name="")
210
+ Elf::Policy.section_symbol_name(file_name,name).tap{|x| @policy.imported_symbols[x] = true}
211
+ end
212
+ end
213
+ class TagBuilder
214
+ include BuilderHelper
215
+ attr_accessor :ranges
216
+ def initialize(pol)
217
+ @policy = pol
218
+ @ranges = []
219
+ end
220
+ def section(name,file_name="")
221
+ range(section_start(name,file_name))
222
+ end
223
+ def range(low,high=nil)
224
+ @ranges << MemoryRange.new(low,high)
225
+ end
226
+ def symbol(sym)
227
+ range(sym)
228
+ end
229
+ end
230
+ class DataBuilder
231
+ include BuilderHelper
232
+ def initialize(transition)
233
+ @transition = transition
234
+ end
235
+ def read(v=true) #TODO: Unify transitions? Intervaltree?
236
+ @transition.read = v
237
+ end
238
+ def write(v=true)
239
+ @transition.write = v
240
+ @transition.read ||= v
241
+ end
242
+ def exec(v=true)
243
+ @transition.exec = v
244
+ @transition.read ||= v
245
+ end
246
+ end
247
+ class StateBuilder
248
+ include BuilderHelper
249
+ def initialize(from,to,pol)
250
+ @from = from
251
+ @to = to
252
+ @policy = pol
253
+ end
254
+
255
+ {
256
+ text: ".text",
257
+ data: ".data",
258
+ bss:".bss"
259
+ }.each{|function,name|
260
+ define_method function, lambda{|library=''| section(name,library)}
261
+ }
262
+ def call(symbol, parambytes= 0, returnbytes=0)
263
+ raise RuntimeError.new "Call has to have a destination" if @from == @to
264
+ @policy << Call.new(@from,@to, symbol, parambytes, returnbytes)
265
+ end
266
+ def call_noreturn(symbol,parambytes=0)
267
+ call(symbol, parambytes,-1)
268
+ end
269
+ def mem(tag, &block)
270
+ d = Data.new(@from,@to,tag)
271
+ DataBuilder.new(d).instance_eval(&block)
272
+ @policy << d
273
+ end
274
+ def exec(tag)
275
+ mem(tag){
276
+ exec }
277
+ end
278
+ def read(tag)
279
+ mem(tag){
280
+ read
281
+ }
282
+ end
283
+ def write(tag)
284
+ mem(tag){
285
+ write
286
+ }
287
+ end
288
+ def readwrite(tag)
289
+ mem(tag){
290
+ read
291
+ write
292
+ }
293
+ end
294
+
295
+ def to(name,&block)
296
+ raise RuntimeError.new "Cannot nest to{} blocks" if @from != @to# or name == @from
297
+ StateBuilder.new(@from,name, @policy).instance_eval(&block)
298
+ end
299
+ def transition(trans)
300
+ @policy << trans
301
+ end
302
+ end
303
+ class PolicyBuilder
304
+ include BuilderHelper
305
+ attr_reader :policy
306
+ def initialize()
307
+ @policy = Policy.new()
308
+ end
309
+ def state(name, &block)
310
+ StateBuilder.new(name,name,@policy).instance_eval(&block)
311
+ end
312
+ def tag(name, &block)
313
+ policy.tags[name] ||= []
314
+ x =TagBuilder.new(@policy)
315
+ x.instance_eval(&block)
316
+ policy.tags[name] += x.ranges
317
+ end
318
+ def call_noreturn(from,to,symbol, parambytes=0)
319
+ @policy << Call.new(from,to, symbol, parambytes,-1)
320
+ end
321
+ def read
322
+ @policy << Data.new(from,to,from,to )
323
+ end
324
+ def start(name)
325
+ @policy.start = name
326
+ end
327
+ end
328
+ def self.build(&block)
329
+ x= PolicyBuilder.new()
330
+ x.instance_eval(&block)
331
+ x.policy
332
+ end
333
+ end
334
+
335
+ end
@@ -0,0 +1,3 @@
1
+ module Mithril
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,774 @@
1
+ require_relative 'elf'
2
+ module Elf
3
+ module Writer
4
+ def self.to_file(filename,elf)
5
+ Elf::Writer::Writer.to_file(filename,elf)
6
+ end
7
+ def self.elf_hash(value)
8
+ h=0
9
+ g=0
10
+ value.chars.map(&:ord).each {|char|
11
+ h = ((h << 4) + (char % 256) )
12
+ g = h & 0xf0000000
13
+ if g!=0
14
+ h = h ^ (g>> 24) # This simulates overflow; elf spec is clever
15
+ end
16
+ h &= ~g
17
+ }
18
+ h
19
+ end
20
+ def self.gnu_hash(value)
21
+ h = 5381
22
+ value.chars.map(&:ord).each {|char|
23
+ h = (h*33 + char) & 0xffffffff
24
+ }
25
+ pp "Gnu_hash #{value} #{h}"
26
+ h
27
+ end
28
+ class StringTable #Replace with compacting string table
29
+ attr_reader :buf
30
+ def initialize
31
+ @buf = StringIO.new("\0")
32
+ @buf.seek 1
33
+ @strings = {} #TODO: Do substring matching, compress the string
34
+ #table.
35
+ # Actually, make all string tables except dynstr one, might save
36
+ # a bit
37
+ end
38
+ def add_string(string)
39
+ unless @strings.include? string
40
+ @strings[string] = @buf.tell.tap {|x|
41
+ BinData::Stringz::new(string).write(@buf)
42
+ }
43
+ end
44
+ @strings[string]
45
+ end
46
+ def buf()
47
+ @buf
48
+ end
49
+ end
50
+ class OutputSection
51
+ attr_accessor :name, :type,:flags,:vaddr,:siz,:link,:info, :align, :entsize, :data, :shdr, :off
52
+ attr_accessor :index
53
+ # TODO: Use params array
54
+ def initialize(name,type,flags,vaddr,siz,link,info,align,entsize,data) #link and info are strings, offset is done by output stage
55
+ @name,@type,@flags, @vaddr, @siz, @link, @info, @align, @entsize, @data= name,type,flags,vaddr,siz,link,info,align,entsize, data
56
+ @off = nil
57
+ @index = nil
58
+ end
59
+ def end
60
+ @vaddr + @siz
61
+ end
62
+
63
+ end
64
+
65
+ class Layout
66
+ def initialize(factory)
67
+ @layout_by_flags = {}
68
+ @layout = RBTree.new()
69
+ @shstrtab = StringTable.new()
70
+ @factory = factory
71
+ @phdrs = [] #BinData::Array.new(:type => @factory.phdr)
72
+ @unallocated =[]
73
+ @sections = [OutputSection.new("", SHT::SHT_NULL, 0,0,0,0,0,0,0,"")]
74
+ @sections[0].index = 0
75
+
76
+ @pinned_sections = {} # Name to vaddr
77
+ end
78
+ def pin_section(name,size,vaddr)
79
+ x = OutputSection.new(".pin_dummy",SHT::SHT_NULL,0,vaddr,0,0,0,0,0,
80
+ BinData::Array.new(type: :int8, initial_length: size).to_binary_s)
81
+ raise RuntimeError.new "Invalid pin" unless range_available?(@layout,vaddr,vaddr+size)
82
+ @layout[vaddr] = x
83
+ @pinned_sections[name] = vaddr
84
+ end
85
+ def [](idx)
86
+ @sections[idx]
87
+ end
88
+ def add(*sections) #Ordering as follows: Fixed
89
+ #(non-nil vaddrs) go where they have to go
90
+ # Flexible sections are added to lowest hole after section of
91
+ # the same type
92
+ retval = []
93
+ return [] if sections.empty?
94
+ sections.each{|sect|
95
+ sect.index = @sections.size
96
+ expect_value "Correct size", sect.data.bytesize, sect.siz
97
+ @sections << sect
98
+ retval << sect.index
99
+ }
100
+ flags = sections.first.flags
101
+ @layout_by_flags[flags] ||= RBTree.new()
102
+ if sections.length > 1 || sections.first.vaddr.nil?
103
+ sections.each { |i|
104
+ expect_value "All adjacent sections have the same flags", i.flags,flags
105
+ expect_value "All sections must float", i.vaddr, nil
106
+ }
107
+ if sections.length == 1 && @pinned_sections.include?(sections.first.name)
108
+ sections.first.vaddr = @pinned_sections[sections.first.name]
109
+ @layout.delete sections.first.vaddr
110
+ @pinned_sections.delete @sections.first.name
111
+ else
112
+ allocate_sections(sections)
113
+ end
114
+ end
115
+ #TODO: Handle the damn nobits
116
+ if(flags & SHF::SHF_ALLOC == 0)
117
+ sections.each {|i| @unallocated << i}
118
+ else
119
+ sections.each {|i|
120
+
121
+ expect_value "Sections shouldn't overlap", range_available?(@layout,i.vaddr,i.end), true
122
+ @layout[i.vaddr] = i
123
+ @layout_by_flags[i.flags][i.vaddr] = i
124
+ }
125
+ end
126
+ retval
127
+ end
128
+ def add_with_phdr(sections,type,flags)
129
+ raise RuntimeError.new "need one section in phdr" if sections.empty?
130
+ x=self.add *sections
131
+ @phdrs << [sections,type,flags]
132
+ x
133
+ end
134
+ def shstrtab(buf) # Last section written, TODO: Move to layout
135
+ name = ".shstrtab"
136
+ idx = @shstrtab.add_string name
137
+ x=@factory.shdr.new
138
+ x.name= idx
139
+ x.type = SHT::SHT_STRTAB
140
+ x.off = buf.tell
141
+ x.siz = @shstrtab.buf.size
142
+ buf.write @shstrtab.buf.string
143
+ x
144
+ end
145
+
146
+ LoadMap = Struct.new(:off,:end,:vaddr,:memsz,:flags,:align)
147
+ def pagealign_loadmap(loadmaps)
148
+ #TODO: Be smarter about this..
149
+ loadmaps.sort_by(&:vaddr).each_cons(2) do |low,high|
150
+ if rounddown(low.vaddr + low.memsz,PAGESIZE) >= rounddown(high.vaddr,PAGESIZE)
151
+ low.flags |= high.flags
152
+ high.flags |= low.flags
153
+ end
154
+ end
155
+ loadmaps
156
+ end
157
+ attr_accessor :tbss_size
158
+ def write_phdrs(buf,filehdr,load)
159
+ phdrs = BinData::Array.new(:type => @factory.phdr)
160
+ expect_value "Too many PHDRS",true, @phdrs.size + load.size < RESERVED_PHDRS
161
+ @phdrs.each {|x|
162
+ sections, type,flags =*x
163
+ x = @factory.phdr.new
164
+ x.align = sections.map(&:align).max
165
+ x.vaddr = sections.map(&:vaddr).min
166
+ x.paddr = x.vaddr
167
+ x.filesz = sections.map(&:end).max - x.vaddr
168
+ if type == PT::PT_TLS and @tbss_size #HACK
169
+ x.memsz = x.filesz + @tbss_size
170
+ else
171
+ x.memsz = x.filesz
172
+ end
173
+ x.flags = flags
174
+ x.off = sections.map(&:off).min
175
+ x.type = type
176
+ phdrs.push x
177
+ }
178
+ #
179
+ load.first.andand.tap{|l|
180
+ l.vaddr -= l.off
181
+ l.memsz += l.off
182
+ l.off = 0
183
+ }
184
+ pagealign_loadmap(load)
185
+ load.each {|l|
186
+ phdrs.push @factory.phdr.new.tap {|x|
187
+ x.align = [PAGESIZE,l.align].max
188
+ x.vaddr = l.vaddr
189
+ x.paddr = x.vaddr
190
+ x.type = PT::PT_LOAD
191
+ x.filesz = l.end - l.off#TODO: handle nobits
192
+ x.memsz = x.filesz # l.memsz
193
+ x.off = l.off
194
+ x.flags = PF::PF_R
195
+ x.flags |= PF::PF_W if(l.flags & SHF::SHF_WRITE != 0)
196
+ x.flags |= PF::PF_X if(l.flags & SHF::SHF_EXECINSTR != 0)
197
+
198
+ }
199
+ }
200
+
201
+ #phdrs.unshift @factory.phdr.new.tap {|x|
202
+ # x.align = 4
203
+ # x.vaddr =
204
+ # x.type = PT::PT_PHDRS
205
+ #}
206
+ # pp load
207
+ filehdr.phoff = buf.tell
208
+ filehdr.phentsize = @factory.phdr.new.num_bytes
209
+ filehdr.phnum = phdrs.size
210
+ phdrs.write buf
211
+
212
+ end
213
+ RESERVED_PHDRS = 16
214
+ def write_sections(buf,filehdr)
215
+ first_shdr = @sections.first
216
+ first_shdr.index = 0
217
+
218
+ #Get more clever about mapping files
219
+ # We put actual program headers right at the beginning.
220
+ phdr_off = buf.tell
221
+ buf.seek phdr_off + RESERVED_PHDRS * @factory.phdr.new.num_bytes
222
+ offset = buf.tell
223
+ @sections[0].off = 0
224
+ (@layout.to_a.sort_by(&:first).map(&:last) + @unallocated).each {|s|
225
+ if s.flags & SHF::SHF_ALLOC != 0
226
+ offset = align_mod(offset,s.vaddr, PAGESIZE)
227
+ end
228
+ s.off = offset
229
+ offset += s.data.bytesize
230
+
231
+ }
232
+ idx = 0
233
+ @sections.each{|s| expect_value "Size field correct", s.siz, s.data.bytesize}
234
+ @sections.sort_by(&:off).each_cons(2){|low,high|
235
+ expect_value "Offsets should not overlap", true, low.off + low.data.bytesize <= high.off
236
+ }
237
+ shdrs = BinData::Array.new(:type=>@factory.shdr).push *@sections.map{ |s|
238
+ expect_value "aligned to vaddr", 0,s.vaddr % s.align if s.align != 0
239
+ expect_value "idx", idx,s.index
240
+ idx +=1
241
+ #expect_value "aligned to pagesize",0, PAGESIZE % s.align
242
+
243
+ buf.seek s.off
244
+ old_off = buf.tell
245
+ buf.write(s.data)
246
+ # pp "#{idx }#{s.name} written to offset #{old_off} to #{buf.tell}"
247
+
248
+
249
+ expect_value "size", s.data.bytesize, s.siz
250
+ link_value = lambda do |name|
251
+ if name.is_a? String
252
+ @sections.to_enum.with_index.select {|sect,idx| sect.name == name}.first.last rescue raise RuntimeError.new("Invalid Section reference #{name}") #Index of first match TODO: check unique
253
+ else
254
+ name || 0
255
+ end
256
+ end
257
+ x= @factory.shdr.new
258
+ x.name = @shstrtab.add_string(s.name)
259
+ x.type = s.type
260
+ x.flags = s.flags
261
+ x.vaddr = s.vaddr
262
+ x.off = s.off
263
+ x.siz = s.siz
264
+ x.link = link_value.call(s.link)
265
+ x.info = link_value.call(s.info)
266
+ x.addralign = s.align
267
+ x.entsize= s.entsize
268
+ # x.flags |= SHF::SHF_ALLOC if(@file.gnu_tls.tbss == s)
269
+ x
270
+ }
271
+ #remove
272
+ mapped = @sections.select{|x| x.flags & SHF::SHF_ALLOC != 0}.sort_by(&:vaddr)
273
+ mapped.each_cons(2){|low,high| expect_value "Mapped sections should not overlap", true,low.vaddr + low.siz<= high.vaddr}
274
+ load = mapped.group_by(&:flags).map{|flags,sections|
275
+ sections.group_by{|x| x.vaddr - x.off}.map do |align,sections|
276
+ memsize = sections.last.off + sections.last.siz - sections.first.off
277
+ # if sections.last.type == SHT::SHT_ end
278
+ # sections.select{|x| x.type == SHT::SHT_NOBITS}.tap {|nobits|
279
+ # if nobits.size > 0
280
+ # expect_value "At most one NOBITS", nobits.size,1
281
+ # expect_value "NOBITS is the last section",sections.last.type, SHT::SHT_NOBITS
282
+ # last_file_addr = nobits.first.vaddr
283
+ # memsize = sections.last.off - sections.first.off
284
+ # end
285
+ # }
286
+ LoadMap.new(sections.first.off, sections.last.off + sections.last.siz,
287
+ sections.first.vaddr, memsize ,flags,sections.map(&:align).max) #TODO: LCM?
288
+ end
289
+ }.flatten
290
+ buf.seek 0,IO::SEEK_END
291
+
292
+ shdrs << shstrtab(buf)
293
+ filehdr.shstrndx = shdrs.size - 1
294
+ filehdr.shoff = buf.tell
295
+ filehdr.shentsize = shdrs[0].num_bytes
296
+ filehdr.shnum = shdrs.size
297
+ shdrs.write buf
298
+
299
+ buf.seek roundup(buf.tell, PAGESIZE)-1
300
+ buf.write '\0' # pad to pagesize
301
+ buf.seek phdr_off
302
+ write_phdrs(buf,filehdr,load)
303
+ end
304
+ private
305
+ def max_address()
306
+ unless @layout.last.nil?
307
+ @layout.last[1].end
308
+ else
309
+ 0x40000 # TODO: Make start_address a parameter
310
+ end
311
+ end
312
+ def allocate_sections(chunk)
313
+ return unless chunk.first.flags & SHF::SHF_ALLOC != 0
314
+ align = chunk.max_by{ |x| x.align}.align #Should technically be the
315
+ #lcm
316
+
317
+ #TODO: check that they are powers of two
318
+ size = chunk.reduce(0) {|i,x| i+roundup(x.siz,align)}
319
+ addr = @layout_by_flags[chunk.first.flags].last.andand { |x| roundup(x[1].end,align)}
320
+ if(addr.nil? or !range_available?(@layout,addr,addr+size))
321
+ addr = roundup(max_address,align)
322
+ expect_value "Address space has room", range_available?(@layout,addr,addr+size),true #TODO: This can actually overflow! Properly do arithemetic mod 2**64
323
+ end
324
+ chunk.each do |section|
325
+ section.vaddr = roundup(addr,align)
326
+ addr = section.end
327
+ end
328
+ end
329
+ def rounddown(number,align)
330
+ return number if align == 0
331
+ case number % align
332
+ when 0
333
+ number
334
+ else
335
+ number - (number % align)
336
+ end
337
+ end
338
+ def roundup(number, align)
339
+ return number if align == 0
340
+ case number % align
341
+ when 0
342
+ number
343
+ else
344
+ number + align - (number % align)
345
+ end
346
+ end
347
+ def align_mod(number,align,mod)
348
+ align = align % mod
349
+ x = number + align - number % mod
350
+ if(x<number)
351
+ x+mod
352
+ else
353
+ x
354
+ end
355
+ end
356
+ def range_available?(tree,from,to)
357
+ if tree.empty?
358
+ true
359
+ else
360
+ ( (tree.upper_bound(from).andand.last.andand.end || -1) <= from ) &&
361
+ ( (tree.lower_bound(to).andand.last.andand.vaddr || +1.0/0.0) >= to)
362
+ end
363
+ end
364
+ end
365
+ PAGESIZE = 1 << 12 #KLUDGE: largest pagesize , align everything to
366
+
367
+ UINT64_MOD = 2**64
368
+ #TODO: Needs a unique class for 'allocatable' sections.
369
+ #Then just sort, and write them out
370
+ class Writer #TODO: Completely refactor this
371
+
372
+ #pagesizes
373
+ def initialize(file,factory)
374
+ @factory = factory
375
+ @file = file
376
+ @layout = Layout.new(@factory)
377
+ @shdrs= BinData::Array::new(type: @factory.shdr,initial_length: 0)
378
+ @phdrs= BinData::Array::new(type: @factory.phdr,initial_length: 0)
379
+ @buf = StringIO.new()
380
+ @progbit_indices = {}
381
+ @section_vaddrs = {}
382
+
383
+ @file.pinned_sections.andand.each {|name,pin| @layout.pin_section(name,pin[:size],pin[:vaddr])
384
+ }
385
+ write_to_buf
386
+ end
387
+ def self.to_file(filename,elf)
388
+ factory = ElfStructFactory.instance(elf.endian,elf.bits)
389
+ writer = Writer.new(elf,factory)
390
+ IO.write filename,writer.buf.string
391
+ end
392
+ attr_reader :buf
393
+ private
394
+ def progbits
395
+ @file.gnu_tls.andand(&:tbss).andand{|x|
396
+ x.flags &= ~SHF::SHF_ALLOC
397
+ @layout.tbss_size = x.size
398
+ }
399
+ bits = (@file.progbits + @file.nobits).sort {|a,b|( a.addr and b.addr ) ? a.addr <=> b.addr : ( a.addr ? -1 : 1 ) }
400
+ bits.each do |sect|
401
+
402
+ out = OutputSection.new(sect.name,sect.sect_type, sect.flags, sect.addr, sect.size,0,0,sect.align, sect.entsize, sect.data.string)
403
+ # binding.pry if sect.sect_type == SHT::SHT_INIT_ARRAY
404
+ if sect.phdr.nil?
405
+ @layout.add out
406
+ else
407
+ @layout.add_with_phdr [out], sect.phdr, sect.phdr_flags
408
+ end
409
+ @progbit_indices[sect] = out.index
410
+ @section_vaddrs[sect] = out.vaddr
411
+ end
412
+
413
+ end
414
+ def note
415
+ os= @file.notes.map {|name,note|
416
+ OutputSection.new(name, SHT::SHT_NOTE, NOTE_FLAGS, nil, note.num_bytes,0,0,NOTE_ALIGN, NOTE_ENTSIZE,note.to_binary_s)
417
+ }
418
+ @layout.add_with_phdr os, PT::PT_NOTE, PF::PF_R unless os.empty?
419
+ #TODO: add phdr
420
+ end
421
+ def interp
422
+ if(@file.interp)
423
+ interp = BinData::Stringz.new(@file.interp)
424
+ @interp_section = OutputSection.new(".interp",SHT::SHT_PROGBITS, SHF::SHF_ALLOC, nil, interp.num_bytes,0,0,1,0,interp.to_binary_s)
425
+ idx, _ =@layout.add_with_phdr [@interp_section], PT::PT_INTERP, PF::PF_R
426
+ @progbit_indices[".interp"]=idx
427
+ end
428
+ end
429
+ def hashtab(table)
430
+ hashtab = BinData::Array.new(:type => "uint32#{@file.endian == :big ? 'be' : 'le'}".to_sym)
431
+ nbuckets = 5
432
+ nchain = table.size
433
+ buckets = Array.new(nbuckets,0)
434
+ chain = Array.new(nchain,0)
435
+
436
+ table.each {|sym,idx|
437
+
438
+ expect_value "Valid symbol index", idx<table.size,true
439
+ i = Elf::Writer::elf_hash(sym.name) % nbuckets
440
+ if(buckets[i] == 0)
441
+ buckets[i] = idx
442
+ else
443
+ i = buckets[i]
444
+ while(chain[i] != 0)
445
+ i= chain[i]
446
+ end
447
+ chain[i] = idx
448
+ end
449
+ }
450
+ hashtab.assign [nbuckets,nchain] + buckets + chain
451
+
452
+ sect = OutputSection.new(".hash",SHT::SHT_HASH,SHF::SHF_ALLOC,nil, hashtab.num_bytes, ".dynsym", 0,8,@file.bits/8, hashtab.to_binary_s)
453
+ @layout.add sect
454
+ @dynamic << @factory.dyn.new(tag: DT::DT_HASH, val: sect.vaddr)
455
+ # pp hashtab.snapshot
456
+ end
457
+ def versions(dynsym,dynstrtab) #TODO: Use parser combinator
458
+ @versions = {}
459
+ gnu_versions = dynsym.map(&:gnu_version).uniq.select{|x|x.is_a? GnuVersion}
460
+
461
+ defined_versions = gnu_versions.select{|x| x.needed == false}
462
+ unless @file.dynamic.gnu_version_basename.nil?
463
+ defined_versions.unshift GnuVersion.new(@file.dynamic.soname,@file.dynamic.gnu_version_basename, ElfFlags::GnuVerFlags::VERFLAG_BASE, false)
464
+ end
465
+ buffer = StringIO.new()
466
+ defined_versions.each_with_index {|ver,definedidx|
467
+ expect_value "Defined SONAME",false, @file.dynamic.gnu_version_basename.nil?
468
+ expect_value "Defined version file name",ver.file, @file.dynamic.soname
469
+ verdef = @factory.verdef.new
470
+ verdef.version = 1
471
+ verdef.flags = ver.flags
472
+ if ver.flags & ElfFlags::GnuVerFlags::VERFLAG_BASE != 0
473
+ verdef.ndx = 1
474
+ else
475
+ verdef.ndx = @versions.size + 2
476
+ @versions[ver] = @versions.size + 2
477
+ end
478
+ verdef.hsh = Elf::Writer::elf_hash(ver.version)
479
+ verdaux = BinData::Array.new(type: @factory.verdaux)
480
+ verdaux << @factory.verdaux.new.tap{|x|
481
+ x.name = dynstrtab.add_string(ver.version)
482
+ x.nextoff = if(ver.parents.size == 0)
483
+ 0
484
+ else
485
+ x.num_bytes
486
+ end
487
+ }
488
+ ver.parents.each_with_index {|parent,idx|
489
+ aux = @factory.verdaux.new
490
+ aux.name = dynstrtab.add_string(parent.version)
491
+ aux.nextoff = if(idx == ver.parents.size - 1)
492
+ 0
493
+ else
494
+ aux.num_bytes
495
+ end
496
+ verdaux << aux
497
+ }
498
+ verdef.cnt = verdaux.size
499
+ verdef.aux = verdef.num_bytes
500
+ if(defined_versions.size - 1 == definedidx)
501
+ verdef.nextoff = 0
502
+ else
503
+ verdef.nextoff = verdaux.num_bytes + verdef.num_bytes
504
+ end
505
+ verdef.write(buffer)
506
+ verdaux.write(buffer)
507
+ }
508
+ unless defined_versions.empty?
509
+ sect = OutputSection.new(".gnu.version_d", SHT::SHT_GNU_VERDEF,SHF::SHF_ALLOC,nil,buffer.size,".dynstr",defined_versions.size,8,0,buffer.string)
510
+ @layout.add sect
511
+ @dynamic << @factory.dyn.new(tag: DT::DT_VERDEF, val: sect.vaddr)
512
+ @dynamic << @factory.dyn.new(tag: DT::DT_VERDEFNUM, val: defined_versions.size)
513
+ end
514
+ buffer = StringIO.new()
515
+ needed_versions = gnu_versions.select{|x| x.needed == true}.group_by(&:file)
516
+ needed_idx = 0
517
+ needed_versions.each {|file, versions|
518
+ needed_idx+=1
519
+ #Create VERNEED for this file
520
+ verneed = @factory.verneed.new
521
+ verneed.version = 1
522
+ verneed.cnt = versions.size
523
+ verneed.file = dynstrtab.add_string(file)
524
+ verneed.aux = verneed.num_bytes
525
+ vernauxs = BinData::Array.new(type: @factory.vernaux)
526
+ versions.each {|ver|
527
+ vernauxs.push @factory.vernaux.new.tap {|x|
528
+ x.hsh = Elf::Writer::elf_hash(ver.version)
529
+ x.flags = ver.flags
530
+ x.other = @versions.size + 2
531
+ x.name = dynstrtab.add_string(ver.version)
532
+ x.nextoff = if vernauxs.size == versions.size - 1
533
+ 0
534
+ else
535
+ x.num_bytes
536
+ end
537
+ @versions[ver] = x.other.to_i
538
+ }
539
+ }
540
+ if (needed_versions.size == needed_idx)
541
+ verneed.nextoff = 0 # 0 for last
542
+ else
543
+ verneed.nextoff = verneed.num_bytes + vernauxs.num_bytes
544
+ end
545
+ verneed.write(buffer)
546
+ vernauxs.write(buffer)
547
+ }
548
+ unless needed_versions.empty?
549
+ sect = OutputSection.new(".gnu.version_r", SHT::SHT_GNU_VERNEED,SHF::SHF_ALLOC,nil,buffer.size,".dynstr",needed_versions.size,8,0,buffer.string)
550
+ @layout.add sect
551
+ @dynamic << @factory.dyn.new(tag: DT::DT_VERNEED, val: sect.vaddr)
552
+ @dynamic << @factory.dyn.new(tag: DT::DT_VERNEEDNUM, val: needed_versions.size)
553
+ end
554
+
555
+ @versions
556
+ end
557
+ def versym(versions,symbols)
558
+ return if versions.empty?
559
+ data = BinData::Array.new(type: @factory.versym)
560
+ symbols.each{|sym|
561
+ veridx = case sym.gnu_version
562
+ when :local
563
+ 0
564
+ when :global
565
+ 1
566
+ else
567
+ versions[sym.gnu_version]
568
+ end
569
+ data.push @factory.versym.new(veridx: veridx)
570
+ }
571
+ sect = OutputSection.new(".gnu.version",SHT::SHT_GNU_VERSYM, SHF::SHF_ALLOC, nil, data.num_bytes, ".dynsym",0, 8,2, data.to_binary_s)
572
+ @layout.add sect
573
+ @dynamic << @factory.dyn.new(tag: DT::DT_VERSYM, val: sect.vaddr)
574
+ end
575
+ def dynsym(dynstrtab)
576
+ symtab = BinData::Array.new(:type => @factory.sym) #TODO: use initial length here to save on
577
+ #some allocations
578
+ syms = [Elf::Symbol.new("",nil,STT::STT_NOTYPE,0,STB::STB_LOCAL,0).tap{
579
+ |x| x.gnu_version = :local; x.semantics = 0}] + @file.symbols.select(&:is_dynamic)
580
+ versions = versions(syms, dynstrtab)
581
+ @dynsym = {}
582
+ #symtab << @factory.sym.new
583
+ syms.each_with_index.each do |sym,idx|
584
+ s = @factory.sym.new
585
+ s.name = dynstrtab.add_string(sym.name)
586
+ s.type = sym.type
587
+ s.binding = sym.bind
588
+ if sym.semantics.nil?
589
+ s.shndx = @progbit_indices[sym.section] || nil
590
+ unless s.shndx
591
+ pp "warning, symbol ", s,"does not have a valid progbits index"
592
+ s.shndx = nil
593
+ end
594
+ else
595
+ s.shndx = sym.semantics
596
+ end
597
+ s.other = sym.visibility
598
+ unless sym.section.nil?
599
+ expect_value "valid symbol offset", sym.sectoffset <= sym.section.size,true #Symbol can point to end of section
600
+ end
601
+
602
+ s.val = (@layout[s.shndx.to_i].andand.vaddr || 0) + sym.sectoffset #TODO: find output section
603
+
604
+ s.siz = sym.size
605
+ @dynsym[sym] = symtab.size
606
+ symtab.push s
607
+ end
608
+ last_local_idx = syms.to_enum.with_index.select{|v,i| v.bind == STB::STB_LOCAL}.andand.last.andand.last || -1
609
+ dynsym = OutputSection.new(".dynsym",SHT::SHT_DYNSYM, SHF::SHF_ALLOC, nil,symtab.num_bytes,".dynstr", last_local_idx+1,1,@factory.sym.new.num_bytes, symtab.to_binary_s)
610
+ versym(versions,syms)
611
+ @layout.add dynsym
612
+ hashtab(@dynsym)
613
+ @dynamic << @factory.dyn.new(tag: DT::DT_SYMTAB,val: dynsym.vaddr)
614
+ @dynamic << @factory.dyn.new(tag: DT::DT_SYMENT,val: @factory.sym.new.num_bytes)
615
+
616
+ end # Produce a hash and a GNU_HASH as well
617
+ def dynamic
618
+ @dynamic = BinData::Array.new(:type => @factory.dyn)
619
+ dynstrtab = StringTable.new()
620
+ reladyn(dynstrtab)
621
+ #PHDR
622
+ @file.dynamic.needed.each{|lib|
623
+ @dynamic << @factory.dyn.new(tag: DT::DT_NEEDED, val: dynstrtab.add_string(lib))
624
+ }
625
+ section_tag = lambda {|tag,value|
626
+ unless(value.nil?)
627
+ @dynamic << @factory.dyn.new(tag: tag, val: value.addr)
628
+ true
629
+ else
630
+ nil
631
+ end
632
+ }
633
+ unless @file.dynamic.soname.nil?
634
+ @dynamic << @factory.dyn.new(tag: DT::DT_SONAME, val: dynstrtab.add_string(@file.dynamic.soname))
635
+ end
636
+ unless @file.dynamic.rpath.nil?
637
+ @dynamic << @factory.dyn.new(tag: DT::DT_RPATH, val: dynstrtab.add_string(@file.dynamic.rpath))
638
+ end
639
+ section_tag.call(DT::DT_PLTGOT,@file.dynamic.pltgot)
640
+ if section_tag.call(DT::DT_INIT_ARRAY,@file.dynamic.init_array)
641
+ @dynamic << @factory.dyn.new(tag: DT::DT_INIT_ARRAYSZ,val: @file.dynamic.init_array.size)
642
+ end
643
+ if section_tag.call(DT::DT_FINI_ARRAY,@file.dynamic.fini_array)
644
+ @dynamic << @factory.dyn.new(tag: DT::DT_FINI_ARRAYSZ,val: @file.dynamic.fini_array.size)
645
+ end
646
+ # section_tag.call(DT::DT_INIT,@file.dynamic.init)
647
+ # section_tag.call(DT::DT_FINI,@file.dynamic.fini)
648
+ @dynamic << @factory.dyn.new(tag: DT::DT_INIT, val: @file.dynamic.init) if @file.dynamic.init
649
+ @dynamic << @factory.dyn.new(tag: DT::DT_FINI, val: @file.dynamic.fini) if @file.dynamic.fini
650
+
651
+ #@file.dynamic.debug_val.each {|dbg|
652
+ # @dynamic << @factory.dyn.new(tag: DT::DT_DEBUG, val: dbg)
653
+ #}
654
+ #@file.dynamic.extra_dynamic.each{ |extra|
655
+ # @dynamic << @factory.dyn.new(tag: extra[:tag], val: extra[:val])
656
+ #}
657
+ if @file.dynamic.flags
658
+ @dynamic << @factory.dyn.new(tag: DT::DT_FLAGS, val: @file.dynamic.flags)
659
+ end
660
+ if @file.dynamic.flags1
661
+ @dynamic << @factory.dyn.new(tag: DT::DT_FLAGS_1, val: @file.dynamic.flags1)
662
+ end
663
+ #string table
664
+ dynstr = OutputSection.new(".dynstr", SHT::SHT_STRTAB, SHF::SHF_ALLOC,nil, dynstrtab.buf.size, 0,0,1,0,dynstrtab.buf.string)
665
+ @layout.add dynstr
666
+ @dynamic << @factory.dyn.new(tag: DT::DT_STRTAB, val: dynstr.vaddr)
667
+ @dynamic << @factory.dyn.new(tag: DT::DT_STRSZ, val: dynstr.siz)
668
+
669
+ @dynamic << @factory.dyn.new(tag: DT::DT_NULL, val: 0) # End marker
670
+ @layout.add_with_phdr [OutputSection.new(".dynamic", SHT::SHT_DYNAMIC, SHF::SHF_ALLOC | SHF::SHF_WRITE, nil, @dynamic.num_bytes,".dynstr",0,8,@factory.dyn.new.num_bytes,@dynamic.to_binary_s)], PT::PT_DYNAMIC, PF::PF_R
671
+ #dynstr -> STRTAB STRSZ
672
+ #dynsym -> DT_SYMENT, D
673
+ end # Write note, etc for the above
674
+ RELATIVE_RELOCS=[R::R_X86_64_RELATIVE]
675
+ def rela_buffer(relocations)
676
+ relative_rela_count = 0
677
+ buf = BinData::Array.new(:type =>@factory.rela).new.tap{|retval|
678
+ relocations.sort_by{|x| RELATIVE_RELOCS.include?(x.type) ? 0 : 1}.each_with_index {|rel,idx|
679
+ entry = @factory.rela.new
680
+ entry.off = @section_vaddrs[rel.section] + rel.offset
681
+ if rel.symbol.nil?
682
+ entry.sym = ElfFlags::SymbolName::STN_UNDEF
683
+ else
684
+ entry.sym = @dynsym[rel.symbol]
685
+ end
686
+ relative_rela_count +=1 if(RELATIVE_RELOCS.include? rel.type)
687
+ entry.type = rel.type
688
+ entry.addend = rel.addend
689
+ retval.push entry
690
+ }
691
+ }
692
+ [buf, relative_rela_count]
693
+ end
694
+
695
+ def reladyn(dynstrtab)
696
+ @file.relocations.each{ |r|
697
+ r.symbol.andand {|x| x.is_dynamic = true}
698
+ }
699
+ dynsym(dynstrtab)
700
+ relaentsize = @factory.rela.new.num_bytes
701
+
702
+ pltrel = @file.relocations.select(&:is_dynamic).select(&:is_lazy)
703
+ dynrel = @file.relocations.select(&:is_dynamic).select{|x| not x.is_lazy}
704
+ rela_sections = []
705
+ unless dynrel.empty?
706
+ #All other relocations go into .rela.dyn
707
+ relabuf,relacount = rela_buffer(dynrel)
708
+ reladyn = OutputSection.new(".rela.dyn", SHT::SHT_RELA, SHF::SHF_ALLOC,nil, relabuf.num_bytes,".dynsym",0,8, relaentsize, relabuf.to_binary_s)
709
+ rela_sections << reladyn
710
+ @dynamic << @factory.dyn.new(tag: DT::DT_RELACOUNT, val: relacount)
711
+ end
712
+ unless(pltrel.empty?)
713
+ relabuf,_=rela_buffer(pltrel)
714
+ relaplt = OutputSection.new(".rela.plt", SHT::SHT_RELA, SHF::SHF_ALLOC, nil, relabuf.num_bytes,".dynsym",@file.dynamic.pltgot.name,8, relaentsize, relabuf.to_binary_s)
715
+ rela_sections << relaplt
716
+ end
717
+ @layout.add *rela_sections # They need to in this order to produce a correct ld.so
718
+ unless pltrel.empty?
719
+ @dynamic << @factory.dyn.new(tag: DT::DT_PLTRELSZ, val: relabuf.num_bytes)
720
+ @dynamic << @factory.dyn.new(tag: DT::DT_PLTREL, val: DT::DT_RELA)
721
+ @dynamic << @factory.dyn.new(tag: DT::DT_JMPREL, val: relaplt.vaddr)
722
+ end
723
+ unless dynrel.empty?
724
+ @dynamic << @factory.dyn.new(tag: DT::DT_RELA, val: reladyn.vaddr)
725
+ @dynamic << @factory.dyn.new(tag: DT::DT_RELASZ, val: reladyn.siz)
726
+ @dynamic << @factory.dyn.new(tag: DT::DT_RELAENT, val: relaentsize)
727
+ end
728
+ end
729
+ def write_headers
730
+ hdr = @factory.hdr.new
731
+ case @file.endian
732
+ when :big
733
+ hdr.ident.id_data = ElfFlags::IdentData::ELFDATA2MSB
734
+ when :little
735
+ hdr.ident.id_data = ElfFlags::IdentData::ELFDATA2LSB
736
+ else
737
+ raise ArgumentError.new "Invalid endianness"
738
+ end
739
+ case @file.bits
740
+ when 64
741
+ hdr.ident.id_class = ElfFlags::IdentClass::ELFCLASS64
742
+ when 32
743
+ hdr.ident.id_class = ElfFlags::IdentClass::ELFCLASS32
744
+ else
745
+ raise ArgumentError.new "Invalid class"
746
+ end
747
+ hdr.ident.id_version = ElfFlags::Version::EV_CURRENT
748
+ hdr.ehsize = hdr.num_bytes
749
+ hdr.type = @file.filetype
750
+ hdr.machine = @file.machine
751
+ hdr.version = @file.version
752
+ hdr.entry = @file.entry
753
+ hdr.flags = @file.flags
754
+
755
+ @layout.write_sections(@buf,hdr)
756
+
757
+ @buf.seek 0
758
+ hdr.write @buf
759
+ end
760
+ #TODO: Fix things like INTERP
761
+
762
+
763
+ def write_to_buf #Make this memoized
764
+ @buf.seek @factory.hdr.new.num_bytes #Leave space for header.
765
+ #this is pretty bad
766
+ progbits
767
+ interp
768
+ note
769
+ dynamic
770
+ write_headers
771
+ end
772
+ end
773
+ end
774
+ end