|
4 | 4 | module Caotral |
5 | 5 | class Linker |
6 | 6 | class Builder |
| 7 | + include Caotral::Binary::ELF::Utils |
| 8 | + R_X86_64_PC32 = 2 |
| 9 | + R_X86_64_PLT32 = 4 |
7 | 10 | SYMTAB_BIND = { locals: 0, globals: 1, weaks: 2, }.freeze |
8 | 11 | BIND_BY_VALUE = SYMTAB_BIND.invert.freeze |
9 | | - attr_reader :symbols |
| 12 | + RELOCATION_SECTION_NAMES = [".rela.text", ".rel.text"].freeze |
| 13 | + ALLOW_RELOCATION_TYPES = [R_X86_64_PC32, R_X86_64_PLT32].freeze |
10 | 14 |
|
11 | | - def initialize(elf_obj:) |
12 | | - @elf_obj = elf_obj |
| 15 | + attr_reader :symbols, :executable, :debug |
| 16 | + |
| 17 | + def initialize(elf_objs:, executable: true, debug: false) |
| 18 | + @elf_objs = elf_objs |
13 | 19 | @symbols = { locals: Set.new, globals: Set.new, weaks: Set.new } |
| 20 | + @executable, @debug = executable, debug |
14 | 21 | end |
| 22 | + |
| 23 | + def build |
| 24 | + raise Caotral::Binary::ELF::Error, "no ELF objects to link" if @elf_objs.empty? |
| 25 | + elf = Caotral::Binary::ELF.new |
| 26 | + elf_obj = @elf_objs.first |
| 27 | + null_section = Caotral::Binary::ELF::Section.new( |
| 28 | + body: nil, |
| 29 | + section_name: "", |
| 30 | + header: Caotral::Binary::ELF::SectionHeader.new |
| 31 | + ) |
| 32 | + text_section = Caotral::Binary::ELF::Section.new( |
| 33 | + body: String.new, |
| 34 | + section_name: ".text", |
| 35 | + header: Caotral::Binary::ELF::SectionHeader.new |
| 36 | + ) |
| 37 | + strtab_section = Caotral::Binary::ELF::Section.new( |
| 38 | + body: Caotral::Binary::ELF::Section::Strtab.new("\0".b), |
| 39 | + section_name: ".strtab", |
| 40 | + header: Caotral::Binary::ELF::SectionHeader.new |
| 41 | + ) |
| 42 | + symtab_section = Caotral::Binary::ELF::Section.new( |
| 43 | + body: [], |
| 44 | + section_name: ".symtab", |
| 45 | + header: Caotral::Binary::ELF::SectionHeader.new |
| 46 | + ) |
| 47 | + shstrtab_section = Caotral::Binary::ELF::Section.new( |
| 48 | + body: Caotral::Binary::ELF::Section::Strtab.new("\0".b), |
| 49 | + section_name: ".shstrtab", |
| 50 | + header: Caotral::Binary::ELF::SectionHeader.new |
| 51 | + ) |
| 52 | + start_bytes = [0xe8, *[0] * 4, 0x48, 0x89, 0xc7, 0x48, 0xc7, 0xc0, 0x3c, 0x00, 0x00, 0x00, 0x0f, 0x05] |
| 53 | + exec_text_offset = 0x1000 |
| 54 | + base_addr = 0x400000 |
| 55 | + vaddr = base_addr + exec_text_offset |
| 56 | + start_len = start_bytes.length |
| 57 | + sections = [] |
| 58 | + rel_sections = [] |
| 59 | + elf.header = elf_obj.header.dup |
| 60 | + strtab_names = [] |
| 61 | + text_offsets = {} |
| 62 | + text_offset = 0 |
| 63 | + sym_by_elf = Hash.new { |h, k| h[k] = [] } |
| 64 | + @elf_objs.each do |elf_obj| |
| 65 | + text = elf_obj.find_by_name(".text") |
| 66 | + unless text.nil? |
| 67 | + text_section.body << text.body |
| 68 | + text_offsets[elf_obj.object_id] = text_offset |
| 69 | + size = text.body.bytesize |
| 70 | + text_offset += size |
| 71 | + end |
| 72 | + strtab = elf_obj.find_by_name(".strtab") |
| 73 | + strtab.body.names.split("\0").each { |name| strtab_names << name } unless strtab.nil? |
| 74 | + symtab = elf_obj.find_by_name(".symtab") |
| 75 | + base_index = nil |
| 76 | + unless symtab.nil? |
| 77 | + base_index = symtab_section.body.size |
| 78 | + symtab.body.each_with_index do |st, index| |
| 79 | + sym = Caotral::Binary::ELF::Section::Symtab.new |
| 80 | + name, info, other, shndx, value, size = st.build.unpack("L<CCS<Q<Q<") |
| 81 | + sym_by_elf[elf_obj] << sym |
| 82 | + value += text_offsets.fetch(elf_obj.object_id, 0) if shndx != 0 |
| 83 | + sym.set!(name:, info:, other:, shndx:, value:, size:) |
| 84 | + sym.name_string = strtab.body.lookup(name) unless strtab.nil? |
| 85 | + symtab_section.body << sym |
| 86 | + end |
| 87 | + end |
| 88 | + rels = elf_obj.select_by_names(RELOCATION_SECTION_NAMES).map do |section| |
| 89 | + rel_section = Caotral::Binary::ELF::Section.new( |
| 90 | + body: [], |
| 91 | + section_name: section.section_name, |
| 92 | + header: Caotral::Binary::ELF::SectionHeader.new |
| 93 | + ) |
| 94 | + section.body.each do |rel| |
| 95 | + offset = rel.offset + text_offsets.fetch(elf_obj.object_id, 0) |
| 96 | + addend = rel.addend? ? rel.addend : nil |
| 97 | + new_rel = Caotral::Binary::ELF::Section::Rel.new(addend: rel.addend?) |
| 98 | + sym = base_index.nil? ? rel.sym : base_index + rel.sym |
| 99 | + info = (sym << 32) | rel.type |
| 100 | + new_rel.set!(offset:, info:, addend:) |
| 101 | + rel_section.body << new_rel |
| 102 | + end |
| 103 | + rel_section |
| 104 | + end |
| 105 | + rel_sections += rels |
| 106 | + end |
| 107 | + strtab_section.body.names = strtab_names.to_a.sort.join("\0") + "\0" |
| 108 | + sections << null_section |
| 109 | + |
| 110 | + main_sym = symtab_section.body.find { |sym| sym.name_string == "main" } |
| 111 | + raise Caotral::Binary::ELF::Error, "main function not found" if executable && main_sym.nil? |
| 112 | + main_offset = main_sym.nil? ? 0 : main_sym.value + start_len |
| 113 | + start_bytes[1, 4] = num2bytes((main_offset - 5), 4) |
| 114 | + text_section.body.prepend(start_bytes.pack("C*")) |
| 115 | + |
| 116 | + text_section.header.set!( |
| 117 | + type: 1, |
| 118 | + flags: 6, |
| 119 | + addr: vaddr, |
| 120 | + offset: exec_text_offset, |
| 121 | + size: text_section.body.bytesize, |
| 122 | + addralign: 16 |
| 123 | + ) |
| 124 | + |
| 125 | + sections << text_section |
| 126 | + strtab_section.header.set!(type: 3, flags: 0, addralign: 1, entsize: 0) |
| 127 | + sections << strtab_section |
| 128 | + symtab_section.body.each do |sym| |
| 129 | + next if sym.shndx == 0 |
| 130 | + name = strtab_section.body.offset_of(sym.name_string) |
| 131 | + value = sym.value + start_len |
| 132 | + sym.set!(name:, value:) |
| 133 | + end |
| 134 | + |
| 135 | + old_syms = symtab_section.body.dup |
| 136 | + symtab_section.body.sort_by! { |sym| sym.info >> 4 } |
| 137 | + local_count = symtab_section.body.count { |sym| (sym.info >> 4) == SYMTAB_BIND[:locals] } |
| 138 | + |
| 139 | + symtab_section.header.set!( |
| 140 | + type: 2, |
| 141 | + flags: 0, |
| 142 | + link: elf.sections.index(strtab_section), |
| 143 | + info: local_count, |
| 144 | + addralign: 8, |
| 145 | + entsize: 24 |
| 146 | + ) |
| 147 | + |
| 148 | + sections << symtab_section |
| 149 | + |
| 150 | + rel_sections.each { |s| sections << s.dup } |
| 151 | + |
| 152 | + shstrtab_section.header.set!( |
| 153 | + type: 3, |
| 154 | + flags: 0, |
| 155 | + addralign: 1, |
| 156 | + entsize: 0 |
| 157 | + ) |
| 158 | + |
| 159 | + @elf_objs.first.without_sections([".text", ".strtab", ".symtab", ".shstrtab", /\.rela?\./]).each do |section| |
| 160 | + sections << section.dup |
| 161 | + end |
| 162 | + |
| 163 | + sections << shstrtab_section |
| 164 | + |
| 165 | + shstrtab_section_names = [*sections.map(&:section_name), "\0"].join("\0") |
| 166 | + shstrtab_section.body.names = shstrtab_section_names |
| 167 | + |
| 168 | + section_map = Hash.new { |h, k| h[k] = {} } |
| 169 | + @elf_objs.each do |elf_obj| |
| 170 | + elf_obj.sections.each_with_index do |section, index| |
| 171 | + newndx = sections.index { |s| s.section_name == section.section_name } |
| 172 | + section_map[elf_obj][index] = newndx unless newndx.nil? |
| 173 | + end |
| 174 | + end |
| 175 | + |
| 176 | + resolved_index = {} |
| 177 | + symtab_section.body.each_with_index do |sym, index| |
| 178 | + name = sym.name_string |
| 179 | + next if name.empty? || sym.shndx == 0 || sym.bind != 1 |
| 180 | + resolved_index[name] ||= index |
| 181 | + end |
| 182 | + |
| 183 | + sym_by_elf.each do |elf_obj, syms| |
| 184 | + syms.each do |sym| |
| 185 | + next if sym.shndx == 0 |
| 186 | + shndx = section_map[elf_obj][sym.shndx] |
| 187 | + sym.set!(shndx:) |
| 188 | + end |
| 189 | + end |
| 190 | + |
| 191 | + rel_sections.each do |rel_section| |
| 192 | + rel_section.body.each do |rel| |
| 193 | + orig_sym = old_syms[rel.sym] |
| 194 | + next if orig_sym.nil? |
| 195 | + name = orig_sym.name_string |
| 196 | + new_index = resolved_index[name] |
| 197 | + next if new_index.nil? |
| 198 | + rel.set!(info: (new_index << 32) | rel.type) |
| 199 | + end |
| 200 | + |
| 201 | + rel_section.header.set!( |
| 202 | + type: rel_type(rel_section), |
| 203 | + flags: 0, |
| 204 | + link: sections.index(symtab_section), |
| 205 | + info: ref_index(sections, rel_section.section_name), |
| 206 | + addralign: 8, |
| 207 | + entsize: rel_entsize(rel_section) |
| 208 | + ) |
| 209 | + end |
| 210 | + |
| 211 | + rel_sections.each do |rel| |
| 212 | + target = sections[rel.header.info] |
| 213 | + bytes = target.body.dup |
| 214 | + symtab_body = symtab_section.body |
| 215 | + rel.body.each do |entry| |
| 216 | + next unless ALLOW_RELOCATION_TYPES.include?(entry.type) |
| 217 | + sym = symtab_body[entry.sym] |
| 218 | + next if sym.nil? || sym.shndx == 0 |
| 219 | + target_addr = target == text_section ? vaddr : target.header.addr |
| 220 | + sym_addr = sym.shndx >= 0xff00 ? sym.value : sections[sym.shndx].then { |st| st.header.addr + sym.value } |
| 221 | + sym_offset = entry.offset + start_len |
| 222 | + sym_addend = entry.addend? ? entry.addend : bytes[sym_offset, 4].unpack1("l<") |
| 223 | + value = sym_addr + sym_addend - (target_addr + sym_offset) |
| 224 | + bytes[sym_offset, 4] = [value].pack("l<") |
| 225 | + end |
| 226 | + target.body = bytes |
| 227 | + end |
| 228 | + |
| 229 | + sections = sections.reject { |section| RELOCATION_SECTION_NAMES.any? { |name| name === section.section_name.to_s } } if executable |
| 230 | + sections.each { |section| elf.sections << section } |
| 231 | + |
| 232 | + elf |
| 233 | + end |
| 234 | + |
15 | 235 | def resolve_symbols |
16 | | - @elf_obj.find_by_name(".symtab").body.each do |symtab| |
17 | | - name = symtab.name_string |
18 | | - next if name.empty? |
19 | | - info = symtab.info |
20 | | - bind = BIND_BY_VALUE.fetch(info >> 4) |
21 | | - if bind == :globals && @symbols[bind].include?(name) |
22 | | - raise Caotral::Binary::ELF::Error,"cannot add into globals: #{name}" |
| 236 | + @elf_objs.each do |elf_obj| |
| 237 | + elf_obj.find_by_name(".symtab").body.each do |symtab| |
| 238 | + name = symtab.name_string |
| 239 | + next if name.empty? |
| 240 | + info = symtab.info |
| 241 | + bind = BIND_BY_VALUE.fetch(info >> 4) |
| 242 | + if bind == :globals && @symbols[bind].include?(name) && symtab.shndx != 0 |
| 243 | + raise Caotral::Binary::ELF::Error,"cannot add into globals: #{name}" |
| 244 | + end |
| 245 | + @symbols[bind] << name |
23 | 246 | end |
24 | | - @symbols[bind] << name |
25 | 247 | end |
26 | 248 | @symbols |
27 | 249 | end |
| 250 | + |
| 251 | + private |
| 252 | + def ref_index(sections, section_name) |
| 253 | + raise Caotral::Binary::ELF::Error, "invalid section name: #{section_name}" if section_name.nil? |
| 254 | + ref_names = "." + section_name.split(".").filter { |sn| !sn.empty? && sn != "rel" && sn != "rela" }.join(".") |
| 255 | + ref = sections.find { |s| ref_names === s.section_name.to_s } |
| 256 | + sections.index(ref) |
| 257 | + end |
| 258 | + |
| 259 | + def rel_type(section) = section.section_name&.start_with?(".rela.") ? 4 : 9 |
| 260 | + def rel_entsize(section) = section.section_name&.start_with?(".rela.") ? 24 : 16 |
28 | 261 | end |
29 | 262 | end |
30 | 263 | end |
0 commit comments