build/unix/elfhack/elf.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/build/unix/elfhack/elf.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,918 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +#undef NDEBUG
     1.9 +#include <cstring>
    1.10 +#include <assert.h>
    1.11 +#include "elfxx.h"
    1.12 +
    1.13 +template <class endian, typename R, typename T>
    1.14 +void Elf_Ehdr_Traits::swap(T &t, R &r)
    1.15 +{
    1.16 +    memcpy(r.e_ident, t.e_ident, sizeof(r.e_ident));
    1.17 +    r.e_type = endian::swap(t.e_type);
    1.18 +    r.e_machine = endian::swap(t.e_machine);
    1.19 +    r.e_version = endian::swap(t.e_version);
    1.20 +    r.e_entry = endian::swap(t.e_entry);
    1.21 +    r.e_phoff = endian::swap(t.e_phoff);
    1.22 +    r.e_shoff = endian::swap(t.e_shoff);
    1.23 +    r.e_flags = endian::swap(t.e_flags);
    1.24 +    r.e_ehsize = endian::swap(t.e_ehsize);
    1.25 +    r.e_phentsize = endian::swap(t.e_phentsize);
    1.26 +    r.e_phnum = endian::swap(t.e_phnum);
    1.27 +    r.e_shentsize = endian::swap(t.e_shentsize);
    1.28 +    r.e_shnum = endian::swap(t.e_shnum);
    1.29 +    r.e_shstrndx = endian::swap(t.e_shstrndx);
    1.30 +}
    1.31 +
    1.32 +template <class endian, typename R, typename T>
    1.33 +void Elf_Phdr_Traits::swap(T &t, R &r)
    1.34 +{
    1.35 +    r.p_type = endian::swap(t.p_type);
    1.36 +    r.p_offset = endian::swap(t.p_offset);
    1.37 +    r.p_vaddr = endian::swap(t.p_vaddr);
    1.38 +    r.p_paddr = endian::swap(t.p_paddr);
    1.39 +    r.p_filesz = endian::swap(t.p_filesz);
    1.40 +    r.p_memsz = endian::swap(t.p_memsz);
    1.41 +    r.p_flags = endian::swap(t.p_flags);
    1.42 +    r.p_align = endian::swap(t.p_align);
    1.43 +}
    1.44 +
    1.45 +template <class endian, typename R, typename T>
    1.46 +void Elf_Shdr_Traits::swap(T &t, R &r)
    1.47 +{
    1.48 +    r.sh_name = endian::swap(t.sh_name);
    1.49 +    r.sh_type = endian::swap(t.sh_type);
    1.50 +    r.sh_flags = endian::swap(t.sh_flags);
    1.51 +    r.sh_addr = endian::swap(t.sh_addr);
    1.52 +    r.sh_offset = endian::swap(t.sh_offset);
    1.53 +    r.sh_size = endian::swap(t.sh_size);
    1.54 +    r.sh_link = endian::swap(t.sh_link);
    1.55 +    r.sh_info = endian::swap(t.sh_info);
    1.56 +    r.sh_addralign = endian::swap(t.sh_addralign);
    1.57 +    r.sh_entsize = endian::swap(t.sh_entsize);
    1.58 +}
    1.59 +
    1.60 +template <class endian, typename R, typename T>
    1.61 +void Elf_Dyn_Traits::swap(T &t, R &r)
    1.62 +{
    1.63 +    r.d_tag = endian::swap(t.d_tag);
    1.64 +    r.d_un.d_val = endian::swap(t.d_un.d_val);
    1.65 +}
    1.66 +
    1.67 +template <class endian, typename R, typename T>
    1.68 +void Elf_Sym_Traits::swap(T &t, R &r)
    1.69 +{
    1.70 +    r.st_name = endian::swap(t.st_name);
    1.71 +    r.st_value = endian::swap(t.st_value);
    1.72 +    r.st_size = endian::swap(t.st_size);
    1.73 +    r.st_info = t.st_info;
    1.74 +    r.st_other = t.st_other;
    1.75 +    r.st_shndx = endian::swap(t.st_shndx);
    1.76 +}
    1.77 +
    1.78 +template <class endian>
    1.79 +struct _Rel_info {
    1.80 +    static inline void swap(Elf32_Word &t, Elf32_Word &r) { r = endian::swap(t); }
    1.81 +    static inline void swap(Elf64_Xword &t, Elf64_Xword &r) { r = endian::swap(t); }
    1.82 +    static inline void swap(Elf64_Xword &t, Elf32_Word &r) {
    1.83 +        r = endian::swap(ELF32_R_INFO(ELF64_R_SYM(t), ELF64_R_TYPE(t)));
    1.84 +    }
    1.85 +    static inline void swap(Elf32_Word &t, Elf64_Xword &r) {
    1.86 +        r = endian::swap(ELF64_R_INFO(ELF32_R_SYM(t), ELF32_R_TYPE(t)));
    1.87 +    }
    1.88 +};
    1.89 +
    1.90 +template <class endian, typename R, typename T>
    1.91 +void Elf_Rel_Traits::swap(T &t, R &r)
    1.92 +{
    1.93 +    r.r_offset = endian::swap(t.r_offset);
    1.94 +    _Rel_info<endian>::swap(t.r_info, r.r_info);
    1.95 +}
    1.96 +
    1.97 +template <class endian, typename R, typename T>
    1.98 +void Elf_Rela_Traits::swap(T &t, R &r)
    1.99 +{
   1.100 +    r.r_offset = endian::swap(t.r_offset);
   1.101 +    _Rel_info<endian>::swap(t.r_info, r.r_info);
   1.102 +    r.r_addend = endian::swap(t.r_addend);
   1.103 +}
   1.104 +
   1.105 +static const Elf32_Shdr null32_section =
   1.106 +    { 0, SHT_NULL, 0, 0, 0, 0, SHN_UNDEF, 0, 0, 0 };
   1.107 +
   1.108 +Elf_Shdr null_section(null32_section);
   1.109 +
   1.110 +Elf_Ehdr::Elf_Ehdr(std::ifstream &file, char ei_class, char ei_data)
   1.111 +: serializable<Elf_Ehdr_Traits>(file, ei_class, ei_data),
   1.112 +  ElfSection(null_section, nullptr, nullptr)
   1.113 +{
   1.114 +    shdr.sh_size = Elf_Ehdr::size(ei_class);
   1.115 +}
   1.116 +
   1.117 +Elf::Elf(std::ifstream &file)
   1.118 +{
   1.119 +    if (!file.is_open())
   1.120 +        throw std::runtime_error("Error opening file");
   1.121 +
   1.122 +    file.exceptions(std::ifstream::eofbit | std::ifstream::failbit | std::ifstream::badbit);
   1.123 +    // Read ELF magic number and identification information
   1.124 +    char e_ident[EI_VERSION];
   1.125 +    file.seekg(0);
   1.126 +    file.read(e_ident, sizeof(e_ident));
   1.127 +    file.seekg(0);
   1.128 +    ehdr = new Elf_Ehdr(file, e_ident[EI_CLASS], e_ident[EI_DATA]);
   1.129 +
   1.130 +    // ELFOSABI_LINUX is kept unsupported because I haven't looked whether
   1.131 +    // STB_GNU_UNIQUE or STT_GNU_IFUNC would need special casing.
   1.132 +    if ((ehdr->e_ident[EI_OSABI] != ELFOSABI_NONE) && (ehdr->e_ident[EI_ABIVERSION] != 0))
   1.133 +        throw std::runtime_error("unsupported ELF ABI");
   1.134 +
   1.135 +    if (ehdr->e_version != 1)
   1.136 +        throw std::runtime_error("unsupported ELF version");
   1.137 +
   1.138 +    // Sanity checks
   1.139 +    if (ehdr->e_shnum == 0)
   1.140 +        throw std::runtime_error("sstripped ELF files aren't supported");
   1.141 +
   1.142 +    if (ehdr->e_ehsize != Elf_Ehdr::size(e_ident[EI_CLASS]))
   1.143 +        throw std::runtime_error("unsupported ELF inconsistency: ehdr.e_ehsize != sizeof(ehdr)");
   1.144 +
   1.145 +    if (ehdr->e_shentsize != Elf_Shdr::size(e_ident[EI_CLASS]))
   1.146 +        throw std::runtime_error("unsupported ELF inconsistency: ehdr.e_shentsize != sizeof(shdr)");
   1.147 +
   1.148 +    if (ehdr->e_phnum == 0) {
   1.149 +        if (ehdr->e_phoff != 0)
   1.150 +            throw std::runtime_error("unsupported ELF inconsistency: e_phnum == 0 && e_phoff != 0");
   1.151 +        if (ehdr->e_phentsize != 0)
   1.152 +            throw std::runtime_error("unsupported ELF inconsistency: e_phnum == 0 && e_phentsize != 0");
   1.153 +    } else if (ehdr->e_phoff != ehdr->e_ehsize)
   1.154 +        throw std::runtime_error("unsupported ELF inconsistency: ehdr->e_phoff != ehdr->e_ehsize");
   1.155 +    else if (ehdr->e_phentsize != Elf_Phdr::size(e_ident[EI_CLASS]))
   1.156 +        throw std::runtime_error("unsupported ELF inconsistency: ehdr->e_phentsize != sizeof(phdr)");
   1.157 +
   1.158 +    // Read section headers
   1.159 +    Elf_Shdr **shdr = new Elf_Shdr *[ehdr->e_shnum];
   1.160 +    file.seekg(ehdr->e_shoff);
   1.161 +    for (int i = 0; i < ehdr->e_shnum; i++)
   1.162 +        shdr[i] = new Elf_Shdr(file, e_ident[EI_CLASS], e_ident[EI_DATA]);
   1.163 +
   1.164 +    // Sanity check in section header for index 0
   1.165 +    if ((shdr[0]->sh_name != 0) || (shdr[0]->sh_type != SHT_NULL) ||
   1.166 +        (shdr[0]->sh_flags != 0) || (shdr[0]->sh_addr != 0) ||
   1.167 +        (shdr[0]->sh_offset != 0) || (shdr[0]->sh_size != 0) ||
   1.168 +        (shdr[0]->sh_link != SHN_UNDEF) || (shdr[0]->sh_info != 0) ||
   1.169 +        (shdr[0]->sh_addralign != 0) || (shdr[0]->sh_entsize != 0))
   1.170 +        throw std::runtime_error("Section header for index 0 contains unsupported values");
   1.171 +
   1.172 +    if ((shdr[ehdr->e_shstrndx]->sh_link != 0) || (shdr[ehdr->e_shstrndx]->sh_info != 0))
   1.173 +        throw std::runtime_error("unsupported ELF content: string table with sh_link != 0 || sh_info != 0");
   1.174 +
   1.175 +    // Store these temporarily
   1.176 +    tmp_shdr = shdr;
   1.177 +    tmp_file = &file;
   1.178 +
   1.179 +    // Fill sections list
   1.180 +    sections = new ElfSection *[ehdr->e_shnum];
   1.181 +    for (int i = 0; i < ehdr->e_shnum; i++)
   1.182 +        sections[i] = nullptr;
   1.183 +    for (int i = 1; i < ehdr->e_shnum; i++) {
   1.184 +        if (sections[i] != nullptr)
   1.185 +            continue;
   1.186 +        getSection(i);
   1.187 +    }
   1.188 +    Elf_Shdr s;
   1.189 +    s.sh_name = 0;
   1.190 +    s.sh_type = SHT_NULL;
   1.191 +    s.sh_flags = 0;
   1.192 +    s.sh_addr = 0;
   1.193 +    s.sh_offset = ehdr->e_shoff;
   1.194 +    s.sh_entsize = Elf_Shdr::size(e_ident[EI_CLASS]);
   1.195 +    s.sh_size = s.sh_entsize * ehdr->e_shnum;
   1.196 +    s.sh_link = 0;
   1.197 +    s.sh_info = 0;
   1.198 +    s.sh_addralign = (e_ident[EI_CLASS] == ELFCLASS32) ? 4 : 8;
   1.199 +    shdr_section = new ElfSection(s, nullptr, nullptr);
   1.200 +
   1.201 +    // Fake section for program headers
   1.202 +    s.sh_offset = ehdr->e_phoff;
   1.203 +    s.sh_addr = ehdr->e_phoff;
   1.204 +    s.sh_entsize = Elf_Phdr::size(e_ident[EI_CLASS]);
   1.205 +    s.sh_size = s.sh_entsize * ehdr->e_phnum;
   1.206 +    phdr_section = new ElfSection(s, nullptr, nullptr);
   1.207 +
   1.208 +    phdr_section->insertAfter(ehdr, false);
   1.209 +
   1.210 +    sections[1]->insertAfter(phdr_section, false);
   1.211 +    for (int i = 2; i < ehdr->e_shnum; i++) {
   1.212 +        // TODO: this should be done in a better way
   1.213 +        if ((shdr_section->getPrevious() == nullptr) && (shdr[i]->sh_offset > ehdr->e_shoff)) {
   1.214 +            shdr_section->insertAfter(sections[i - 1], false);
   1.215 +            sections[i]->insertAfter(shdr_section, false);
   1.216 +        } else
   1.217 +            sections[i]->insertAfter(sections[i - 1], false);
   1.218 +    }
   1.219 +    if (shdr_section->getPrevious() == nullptr)
   1.220 +        shdr_section->insertAfter(sections[ehdr->e_shnum - 1], false);
   1.221 +
   1.222 +    tmp_file = nullptr;
   1.223 +    tmp_shdr = nullptr;
   1.224 +    for (int i = 0; i < ehdr->e_shnum; i++)
   1.225 +        delete shdr[i];
   1.226 +    delete[] shdr;
   1.227 +
   1.228 +    eh_shstrndx = (ElfStrtab_Section *)sections[ehdr->e_shstrndx];
   1.229 +
   1.230 +    // Skip reading program headers if there aren't any
   1.231 +    if (ehdr->e_phnum == 0)
   1.232 +        return;
   1.233 +
   1.234 +    // Read program headers
   1.235 +    file.seekg(ehdr->e_phoff);
   1.236 +    for (int i = 0; i < ehdr->e_phnum; i++) {
   1.237 +        Elf_Phdr phdr(file, e_ident[EI_CLASS], e_ident[EI_DATA]);
   1.238 +        if (phdr.p_type == PT_LOAD) {
   1.239 +            // Default alignment for PT_LOAD on x86-64 prevents elfhack from
   1.240 +            // doing anything useful. However, the system doesn't actually
   1.241 +            // require such a big alignment, so in order for elfhack to work
   1.242 +            // efficiently, reduce alignment when it's originally the default
   1.243 +            // one.
   1.244 +            if ((ehdr->e_machine == EM_X86_64) && (phdr.p_align == 0x200000))
   1.245 +              phdr.p_align = 0x1000;
   1.246 +        }
   1.247 +        ElfSegment *segment = new ElfSegment(&phdr);
   1.248 +        // Some segments aren't entirely filled (if at all) by sections
   1.249 +        // For those, we use fake sections
   1.250 +        if ((phdr.p_type == PT_LOAD) && (phdr.p_offset == 0)) {
   1.251 +            // Use a fake section for ehdr and phdr
   1.252 +            ehdr->getShdr().sh_addr = phdr.p_vaddr;
   1.253 +            phdr_section->getShdr().sh_addr += phdr.p_vaddr;
   1.254 +            segment->addSection(ehdr);
   1.255 +            segment->addSection(phdr_section);
   1.256 +        }
   1.257 +        if (phdr.p_type == PT_PHDR)
   1.258 +            segment->addSection(phdr_section);
   1.259 +        for (int j = 1; j < ehdr->e_shnum; j++)
   1.260 +            if (phdr.contains(sections[j]))
   1.261 +                segment->addSection(sections[j]);
   1.262 +        // Make sure that our view of segments corresponds to the original
   1.263 +        // ELF file.
   1.264 +        assert(segment->getFileSize() == phdr.p_filesz);
   1.265 +        // gold makes TLS segments end on an aligned virtual address, even
   1.266 +        // when the underlying section ends before that, while bfd ld
   1.267 +        // doesn't. It's fine if we don't keep that alignment.
   1.268 +        unsigned int memsize = segment->getMemSize();
   1.269 +        if (phdr.p_type == PT_TLS && memsize != phdr.p_memsz) {
   1.270 +            unsigned int align = segment->getAlign();
   1.271 +            memsize = (memsize + align - 1) & ~(align - 1);
   1.272 +        }
   1.273 +        assert(memsize == phdr.p_memsz);
   1.274 +        segments.push_back(segment);
   1.275 +    }
   1.276 +
   1.277 +    new (&eh_entry) ElfLocation(ehdr->e_entry, this);
   1.278 +}
   1.279 +
   1.280 +Elf::~Elf()
   1.281 +{
   1.282 +    for (std::vector<ElfSegment *>::iterator seg = segments.begin(); seg != segments.end(); seg++)
   1.283 +        delete *seg;
   1.284 +    delete[] sections;
   1.285 +    ElfSection *section = ehdr;
   1.286 +    while (section != nullptr) {
   1.287 +        ElfSection *next = section->getNext();
   1.288 +        delete section;
   1.289 +        section = next;
   1.290 +    }
   1.291 +}
   1.292 +
   1.293 +// TODO: This shouldn't fail after inserting sections
   1.294 +ElfSection *Elf::getSection(int index)
   1.295 +{
   1.296 +    if ((index < -1) || (index >= ehdr->e_shnum))
   1.297 +        throw std::runtime_error("Section index out of bounds");
   1.298 +    if (index == -1)
   1.299 +        index = ehdr->e_shstrndx; // TODO: should be fixed to use the actual current number
   1.300 +    // Special case: the section at index 0 is void
   1.301 +    if (index == 0)
   1.302 +        return nullptr;
   1.303 +    // Infinite recursion guard
   1.304 +    if (sections[index] == (ElfSection *)this)
   1.305 +        return nullptr;
   1.306 +    if (sections[index] == nullptr) {
   1.307 +        sections[index] = (ElfSection *)this;
   1.308 +        switch (tmp_shdr[index]->sh_type) {
   1.309 +        case SHT_DYNAMIC:
   1.310 +            sections[index] = new ElfDynamic_Section(*tmp_shdr[index], tmp_file, this);
   1.311 +            break;
   1.312 +        case SHT_REL:
   1.313 +            sections[index] = new ElfRel_Section<Elf_Rel>(*tmp_shdr[index], tmp_file, this);
   1.314 +            break;
   1.315 +        case SHT_RELA:
   1.316 +            sections[index] = new ElfRel_Section<Elf_Rela>(*tmp_shdr[index], tmp_file, this);
   1.317 +            break;
   1.318 +        case SHT_DYNSYM:
   1.319 +        case SHT_SYMTAB:
   1.320 +            sections[index] = new ElfSymtab_Section(*tmp_shdr[index], tmp_file, this);
   1.321 +            break;
   1.322 +        case SHT_STRTAB:
   1.323 +            sections[index] = new ElfStrtab_Section(*tmp_shdr[index], tmp_file, this);
   1.324 +            break;
   1.325 +        default:
   1.326 +            sections[index] = new ElfSection(*tmp_shdr[index], tmp_file, this);
   1.327 +        }
   1.328 +    }
   1.329 +    return sections[index];
   1.330 +}
   1.331 +
   1.332 +ElfSection *Elf::getSectionAt(unsigned int offset)
   1.333 +{
   1.334 +    for (int i = 1; i < ehdr->e_shnum; i++) {
   1.335 +        ElfSection *section = getSection(i);
   1.336 +        if ((section != nullptr) && (section->getFlags() & SHF_ALLOC) && !(section->getFlags() & SHF_TLS) &&
   1.337 +            (offset >= section->getAddr()) && (offset < section->getAddr() + section->getSize()))
   1.338 +            return section;
   1.339 +    }
   1.340 +    return nullptr;
   1.341 +}
   1.342 +
   1.343 +ElfSegment *Elf::getSegmentByType(unsigned int type, ElfSegment *last)
   1.344 +{
   1.345 +    std::vector<ElfSegment *>::iterator seg;
   1.346 +    if (last) {
   1.347 +        seg = std::find(segments.begin(), segments.end(), last);
   1.348 +        ++seg;
   1.349 +    } else
   1.350 +        seg = segments.begin();
   1.351 +    for (; seg != segments.end(); seg++)
   1.352 +        if ((*seg)->getType() == type)
   1.353 +            return *seg;
   1.354 +    return nullptr;
   1.355 +}
   1.356 +
   1.357 +void Elf::removeSegment(ElfSegment *segment)
   1.358 +{
   1.359 +    if (!segment)
   1.360 +        return;
   1.361 +    std::vector<ElfSegment *>::iterator seg;
   1.362 +    seg = std::find(segments.begin(), segments.end(), segment);
   1.363 +    if (seg == segments.end())
   1.364 +        return;
   1.365 +    segment->clear();
   1.366 +    segments.erase(seg);
   1.367 +}
   1.368 +
   1.369 +ElfDynamic_Section *Elf::getDynSection()
   1.370 +{
   1.371 +    for (std::vector<ElfSegment *>::iterator seg = segments.begin(); seg != segments.end(); seg++)
   1.372 +        if (((*seg)->getType() == PT_DYNAMIC) && ((*seg)->getFirstSection() != nullptr) &&
   1.373 +            (*seg)->getFirstSection()->getType() == SHT_DYNAMIC)
   1.374 +            return (ElfDynamic_Section *)(*seg)->getFirstSection();
   1.375 +
   1.376 +    return nullptr;
   1.377 +}
   1.378 +
   1.379 +void Elf::normalize()
   1.380 +{
   1.381 +    // fixup section headers sh_name; TODO: that should be done by sections
   1.382 +    // themselves
   1.383 +    for (ElfSection *section = ehdr; section != nullptr; section = section->getNext()) {
   1.384 +        if (section->getIndex() == 0)
   1.385 +            continue;
   1.386 +        else
   1.387 +            ehdr->e_shnum = section->getIndex() + 1;
   1.388 +        section->getShdr().sh_name = eh_shstrndx->getStrIndex(section->getName());
   1.389 +    }
   1.390 +    ehdr->markDirty();
   1.391 +    // Check segments consistency
   1.392 +    int i = 0;
   1.393 +    for (std::vector<ElfSegment *>::iterator seg = segments.begin(); seg != segments.end(); seg++, i++) {
   1.394 +        std::list<ElfSection *>::iterator it = (*seg)->begin();
   1.395 +        for (ElfSection *last = *(it++); it != (*seg)->end(); last = *(it++)) {
   1.396 +            if (((*it)->getType() != SHT_NOBITS) &&
   1.397 +                ((*it)->getAddr() - last->getAddr()) != ((*it)->getOffset() - last->getOffset())) {
   1.398 +                    throw std::runtime_error("Segments inconsistency");
   1.399 +            }
   1.400 +        }
   1.401 +    }
   1.402 +    // fixup ehdr before writing
   1.403 +    if (ehdr->e_phnum != segments.size()) {
   1.404 +        ehdr->e_phnum = segments.size();
   1.405 +        phdr_section->getShdr().sh_size = segments.size() * Elf_Phdr::size(ehdr->e_ident[EI_CLASS]);
   1.406 +        phdr_section->getNext()->markDirty();
   1.407 +    }
   1.408 +    // fixup shdr before writing
   1.409 +    if (ehdr->e_shnum != shdr_section->getSize() / shdr_section->getEntSize())
   1.410 +        shdr_section->getShdr().sh_size = ehdr->e_shnum * Elf_Shdr::size(ehdr->e_ident[EI_CLASS]);
   1.411 +    ehdr->e_shoff = shdr_section->getOffset();
   1.412 +    ehdr->e_entry = eh_entry.getValue();
   1.413 +    ehdr->e_shstrndx = eh_shstrndx->getIndex();
   1.414 +}
   1.415 +
   1.416 +void Elf::write(std::ofstream &file)
   1.417 +{
   1.418 +    normalize();
   1.419 +    for (ElfSection *section = ehdr;
   1.420 +         section != nullptr; section = section->getNext()) {
   1.421 +        file.seekp(section->getOffset());
   1.422 +        if (section == phdr_section) {
   1.423 +            for (std::vector<ElfSegment *>::iterator seg = segments.begin(); seg != segments.end(); seg++) {
   1.424 +                Elf_Phdr phdr;
   1.425 +                phdr.p_type = (*seg)->getType();
   1.426 +                phdr.p_flags = (*seg)->getFlags();
   1.427 +                phdr.p_offset = (*seg)->getOffset();
   1.428 +                phdr.p_vaddr = (*seg)->getAddr();
   1.429 +                phdr.p_paddr = phdr.p_vaddr + (*seg)->getVPDiff();
   1.430 +                phdr.p_filesz = (*seg)->getFileSize();
   1.431 +                phdr.p_memsz = (*seg)->getMemSize();
   1.432 +                phdr.p_align = (*seg)->getAlign();
   1.433 +                phdr.serialize(file, ehdr->e_ident[EI_CLASS], ehdr->e_ident[EI_DATA]);
   1.434 +            }
   1.435 +        } else if (section == shdr_section) {
   1.436 +            null_section.serialize(file, ehdr->e_ident[EI_CLASS], ehdr->e_ident[EI_DATA]);
   1.437 +            for (ElfSection *sec = ehdr; sec!= nullptr; sec = sec->getNext()) {
   1.438 +                if (sec->getType() != SHT_NULL)
   1.439 +                    sec->getShdr().serialize(file, ehdr->e_ident[EI_CLASS], ehdr->e_ident[EI_DATA]);
   1.440 +            }
   1.441 +        } else
   1.442 +           section->serialize(file, ehdr->e_ident[EI_CLASS], ehdr->e_ident[EI_DATA]);
   1.443 +    }
   1.444 +}
   1.445 +
   1.446 +ElfSection::ElfSection(Elf_Shdr &s, std::ifstream *file, Elf *parent)
   1.447 +: shdr(s),
   1.448 +  link(shdr.sh_link == SHN_UNDEF ? nullptr : parent->getSection(shdr.sh_link)),
   1.449 +  next(nullptr), previous(nullptr), index(-1)
   1.450 +{
   1.451 +    if ((file == nullptr) || (shdr.sh_type == SHT_NULL) || (shdr.sh_type == SHT_NOBITS))
   1.452 +        data = nullptr;
   1.453 +    else {
   1.454 +        data = new char[shdr.sh_size];
   1.455 +        int pos = file->tellg();
   1.456 +        file->seekg(shdr.sh_offset);
   1.457 +        file->read(data, shdr.sh_size);
   1.458 +        file->seekg(pos);
   1.459 +    }
   1.460 +    if (shdr.sh_name == 0)
   1.461 +        name = nullptr;
   1.462 +    else {
   1.463 +        ElfStrtab_Section *strtab = (ElfStrtab_Section *) parent->getSection(-1);
   1.464 +        // Special case (see elfgeneric.cpp): if strtab is nullptr, the
   1.465 +        // section being created is the strtab.
   1.466 +        if (strtab == nullptr)
   1.467 +            name = &data[shdr.sh_name];
   1.468 +        else
   1.469 +            name = strtab->getStr(shdr.sh_name);
   1.470 +    }
   1.471 +    // Only SHT_REL/SHT_RELA sections use sh_info to store a section
   1.472 +    // number.
   1.473 +    if ((shdr.sh_type == SHT_REL) || (shdr.sh_type == SHT_RELA))
   1.474 +        info.section = shdr.sh_info ? parent->getSection(shdr.sh_info) : nullptr;
   1.475 +    else
   1.476 +        info.index = shdr.sh_info;
   1.477 +}
   1.478 +
   1.479 +unsigned int ElfSection::getAddr()
   1.480 +{
   1.481 +    if (shdr.sh_addr != (Elf32_Word)-1)
   1.482 +        return shdr.sh_addr;
   1.483 +
   1.484 +    // It should be safe to adjust sh_addr for all allocated sections that
   1.485 +    // are neither SHT_NOBITS nor SHT_PROGBITS
   1.486 +    if ((previous != nullptr) && isRelocatable()) {
   1.487 +        unsigned int addr = previous->getAddr();
   1.488 +        if (previous->getType() != SHT_NOBITS)
   1.489 +            addr += previous->getSize();
   1.490 +
   1.491 +        if (addr & (getAddrAlign() - 1))
   1.492 +            addr = (addr | (getAddrAlign() - 1)) + 1;
   1.493 +
   1.494 +        return (shdr.sh_addr = addr);
   1.495 +    }
   1.496 +    return shdr.sh_addr;
   1.497 +}
   1.498 +
   1.499 +unsigned int ElfSection::getOffset()
   1.500 +{
   1.501 +    if (shdr.sh_offset != (Elf32_Word)-1)
   1.502 +        return shdr.sh_offset;
   1.503 +
   1.504 +    if (previous == nullptr)
   1.505 +        return (shdr.sh_offset = 0);
   1.506 +
   1.507 +    unsigned int offset = previous->getOffset();
   1.508 +
   1.509 +    ElfSegment *ptload = getSegmentByType(PT_LOAD);
   1.510 +    ElfSegment *prev_ptload = previous->getSegmentByType(PT_LOAD);
   1.511 +
   1.512 +    if (ptload && (ptload == prev_ptload)) {
   1.513 +        offset += getAddr() - previous->getAddr();
   1.514 +        return (shdr.sh_offset = offset);
   1.515 +    }
   1.516 +
   1.517 +    if (previous->getType() != SHT_NOBITS)
   1.518 +        offset += previous->getSize();
   1.519 +
   1.520 +    Elf32_Word align = 0x1000;
   1.521 +    for (std::vector<ElfSegment *>::iterator seg = segments.begin(); seg != segments.end(); seg++)
   1.522 +        align = std::max(align, (*seg)->getAlign());
   1.523 +
   1.524 +    Elf32_Word mask = align - 1;
   1.525 +    // SHF_TLS is used for .tbss which is some kind of special case.
   1.526 +    if (((getType() != SHT_NOBITS) || (getFlags() & SHF_TLS)) && (getFlags() & SHF_ALLOC)) {
   1.527 +        if ((getAddr() & mask) < (offset & mask))
   1.528 +            offset = (offset | mask) + (getAddr() & mask) + 1;
   1.529 +        else
   1.530 +            offset = (offset & ~mask) + (getAddr() & mask);
   1.531 +    }
   1.532 +    if ((getType() != SHT_NOBITS) && (offset & (getAddrAlign() - 1)))
   1.533 +        offset = (offset | (getAddrAlign() - 1)) + 1;
   1.534 +
   1.535 +    // Two subsequent sections can't be mapped in the same page in memory
   1.536 +    // if they aren't in the same 4K block on disk.
   1.537 +    if ((getType() != SHT_NOBITS) && getAddr()) {
   1.538 +        if (((offset >> 12) != (previous->getOffset() >> 12)) &&
   1.539 +            ((getAddr() >> 12) == (previous->getAddr() >> 12)))
   1.540 +            throw std::runtime_error("Moving section would require overlapping segments");
   1.541 +    }
   1.542 +
   1.543 +    return (shdr.sh_offset = offset);
   1.544 +}
   1.545 +
   1.546 +int ElfSection::getIndex()
   1.547 +{
   1.548 +    if (index != -1)
   1.549 +        return index;
   1.550 +    if (getType() == SHT_NULL)
   1.551 +        return (index = 0);
   1.552 +    ElfSection *reference;
   1.553 +    for (reference = previous; (reference != nullptr) && (reference->getType() == SHT_NULL); reference = reference->getPrevious());
   1.554 +    if (reference == nullptr)
   1.555 +        return (index = 1);
   1.556 +    return (index = reference->getIndex() + 1);
   1.557 +}
   1.558 +
   1.559 +Elf_Shdr &ElfSection::getShdr()
   1.560 +{
   1.561 +    getOffset();
   1.562 +    if (shdr.sh_link == (Elf32_Word)-1)
   1.563 +        shdr.sh_link = getLink() ? getLink()->getIndex() : 0;
   1.564 +    if (shdr.sh_info == (Elf32_Word)-1)
   1.565 +        shdr.sh_info = ((getType() == SHT_REL) || (getType() == SHT_RELA)) ?
   1.566 +                       (getInfo().section ? getInfo().section->getIndex() : 0) :
   1.567 +                       getInfo().index;
   1.568 +
   1.569 +    return shdr;
   1.570 +}
   1.571 +
   1.572 +ElfSegment::ElfSegment(Elf_Phdr *phdr)
   1.573 +: type(phdr->p_type), v_p_diff(phdr->p_paddr - phdr->p_vaddr),
   1.574 +  flags(phdr->p_flags), align(phdr->p_align), vaddr(phdr->p_vaddr),
   1.575 +  filesz(phdr->p_filesz), memsz(phdr->p_memsz) {}
   1.576 +
   1.577 +void ElfSegment::addSection(ElfSection *section)
   1.578 +{
   1.579 +    // Make sure all sections in PT_GNU_RELRO won't be moved by elfhack
   1.580 +    assert(!((type == PT_GNU_RELRO) && (section->isRelocatable())));
   1.581 +
   1.582 +    //TODO: Check overlapping sections
   1.583 +    std::list<ElfSection *>::iterator i;
   1.584 +    for (i = sections.begin(); i != sections.end(); ++i)
   1.585 +        if ((*i)->getAddr() > section->getAddr())
   1.586 +            break;
   1.587 +    sections.insert(i, section);
   1.588 +    section->addToSegment(this);
   1.589 +}
   1.590 +
   1.591 +void ElfSegment::removeSection(ElfSection *section)
   1.592 +{
   1.593 +    sections.remove(section);
   1.594 +    section->removeFromSegment(this);
   1.595 +}
   1.596 +
   1.597 +unsigned int ElfSegment::getFileSize()
   1.598 +{
   1.599 +    if (type == PT_GNU_RELRO || isElfHackFillerSegment())
   1.600 +        return filesz;
   1.601 +
   1.602 +    if (sections.empty())
   1.603 +        return 0;
   1.604 +    // Search the last section that is not SHT_NOBITS
   1.605 +    std::list<ElfSection *>::reverse_iterator i;
   1.606 +    for (i = sections.rbegin(); (i != sections.rend()) && ((*i)->getType() == SHT_NOBITS); ++i);
   1.607 +    // All sections are SHT_NOBITS
   1.608 +    if (i == sections.rend())
   1.609 +        return 0;
   1.610 +
   1.611 +    unsigned int end = (*i)->getAddr() + (*i)->getSize();
   1.612 +
   1.613 +    return end - sections.front()->getAddr();
   1.614 +}
   1.615 +
   1.616 +unsigned int ElfSegment::getMemSize()
   1.617 +{
   1.618 +    if (type == PT_GNU_RELRO || isElfHackFillerSegment())
   1.619 +        return memsz;
   1.620 +
   1.621 +    if (sections.empty())
   1.622 +        return 0;
   1.623 +
   1.624 +    unsigned int end = sections.back()->getAddr() + sections.back()->getSize();
   1.625 +
   1.626 +    return end - sections.front()->getAddr();
   1.627 +}
   1.628 +
   1.629 +unsigned int ElfSegment::getOffset()
   1.630 +{
   1.631 +    if ((type == PT_GNU_RELRO) && !sections.empty() &&
   1.632 +        (sections.front()->getAddr() != vaddr))
   1.633 +        throw std::runtime_error("PT_GNU_RELRO segment doesn't start on a section start");
   1.634 +
   1.635 +    // Neither bionic nor glibc linkers seem to like when the offset of that segment is 0
   1.636 +    if (isElfHackFillerSegment())
   1.637 +        return vaddr;
   1.638 +
   1.639 +    return sections.empty() ? 0 : sections.front()->getOffset();
   1.640 +}
   1.641 +
   1.642 +unsigned int ElfSegment::getAddr()
   1.643 +{
   1.644 +    if ((type == PT_GNU_RELRO) && !sections.empty() &&
   1.645 +        (sections.front()->getAddr() != vaddr))
   1.646 +        throw std::runtime_error("PT_GNU_RELRO segment doesn't start on a section start");
   1.647 +
   1.648 +    if (isElfHackFillerSegment())
   1.649 +        return vaddr;
   1.650 +
   1.651 +    return sections.empty() ? 0 : sections.front()->getAddr();
   1.652 +}
   1.653 +
   1.654 +void ElfSegment::clear()
   1.655 +{
   1.656 +    for (std::list<ElfSection *>::iterator i = sections.begin(); i != sections.end(); ++i)
   1.657 +        (*i)->removeFromSegment(this);
   1.658 +    sections.clear();
   1.659 +}
   1.660 +
   1.661 +ElfValue *ElfDynamic_Section::getValueForType(unsigned int tag)
   1.662 +{
   1.663 +    for (unsigned int i = 0; i < shdr.sh_size / shdr.sh_entsize; i++)
   1.664 +        if (dyns[i].tag == tag)
   1.665 +            return dyns[i].value;
   1.666 +
   1.667 +    return nullptr;
   1.668 +}
   1.669 +
   1.670 +ElfSection *ElfDynamic_Section::getSectionForType(unsigned int tag)
   1.671 +{
   1.672 +    ElfValue *value = getValueForType(tag);
   1.673 +    return value ? value->getSection() : nullptr;
   1.674 +}
   1.675 +
   1.676 +bool ElfDynamic_Section::setValueForType(unsigned int tag, ElfValue *val)
   1.677 +{
   1.678 +    unsigned int i;
   1.679 +    unsigned int shnum = shdr.sh_size / shdr.sh_entsize;
   1.680 +    for (i = 0; (i < shnum) && (dyns[i].tag != DT_NULL); i++)
   1.681 +        if (dyns[i].tag == tag) {
   1.682 +            delete dyns[i].value;
   1.683 +            dyns[i].value = val;
   1.684 +            return true;
   1.685 +        }
   1.686 +    // If we get here, this means we didn't match for the given tag
   1.687 +    // Most of the time, there are a few DT_NULL entries, that we can
   1.688 +    // use to add our value, but if we are on the last entry, we can't.
   1.689 +    if (i >= shnum - 1)
   1.690 +        return false;
   1.691 +
   1.692 +    dyns[i].tag = tag;
   1.693 +    dyns[i].value = val;
   1.694 +    return true;
   1.695 +}
   1.696 +
   1.697 +ElfDynamic_Section::ElfDynamic_Section(Elf_Shdr &s, std::ifstream *file, Elf *parent)
   1.698 +: ElfSection(s, file, parent)
   1.699 +{
   1.700 +    int pos = file->tellg();
   1.701 +    dyns.resize(s.sh_size / s.sh_entsize);
   1.702 +    file->seekg(shdr.sh_offset);
   1.703 +    // Here we assume tags refer to only one section (e.g. DT_RELSZ accounts
   1.704 +    // for .rel.dyn size)
   1.705 +    for (unsigned int i = 0; i < s.sh_size / s.sh_entsize; i++) {
   1.706 +        Elf_Dyn dyn(*file, parent->getClass(), parent->getData());
   1.707 +        dyns[i].tag = dyn.d_tag;
   1.708 +        switch (dyn.d_tag) {
   1.709 +        case DT_NULL:
   1.710 +        case DT_SYMBOLIC:
   1.711 +        case DT_TEXTREL:
   1.712 +        case DT_BIND_NOW:
   1.713 +            dyns[i].value = new ElfValue();
   1.714 +            break;
   1.715 +        case DT_NEEDED:
   1.716 +        case DT_SONAME:
   1.717 +        case DT_RPATH:
   1.718 +        case DT_PLTREL:
   1.719 +        case DT_RUNPATH:
   1.720 +        case DT_FLAGS:
   1.721 +        case DT_RELACOUNT:
   1.722 +        case DT_RELCOUNT:
   1.723 +        case DT_VERDEFNUM:
   1.724 +        case DT_VERNEEDNUM:
   1.725 +            dyns[i].value = new ElfPlainValue(dyn.d_un.d_val);
   1.726 +            break;
   1.727 +        case DT_PLTGOT:
   1.728 +        case DT_HASH:
   1.729 +        case DT_STRTAB:
   1.730 +        case DT_SYMTAB:
   1.731 +        case DT_RELA:
   1.732 +        case DT_INIT:
   1.733 +        case DT_FINI:
   1.734 +        case DT_REL:
   1.735 +        case DT_JMPREL:
   1.736 +        case DT_INIT_ARRAY:
   1.737 +        case DT_FINI_ARRAY:
   1.738 +        case DT_GNU_HASH:
   1.739 +        case DT_VERSYM:
   1.740 +        case DT_VERNEED:
   1.741 +        case DT_VERDEF:
   1.742 +            dyns[i].value = new ElfLocation(dyn.d_un.d_ptr, parent);
   1.743 +            break;
   1.744 +        default:
   1.745 +            dyns[i].value = nullptr;
   1.746 +        }
   1.747 +    }
   1.748 +    // Another loop to get the section sizes
   1.749 +    for (unsigned int i = 0; i < s.sh_size / s.sh_entsize; i++)
   1.750 +        switch (dyns[i].tag) {
   1.751 +        case DT_PLTRELSZ:
   1.752 +            dyns[i].value = new ElfSize(getSectionForType(DT_JMPREL));
   1.753 +            break;
   1.754 +        case DT_RELASZ:
   1.755 +            dyns[i].value = new ElfSize(getSectionForType(DT_RELA));
   1.756 +            break;
   1.757 +        case DT_STRSZ:
   1.758 +            dyns[i].value = new ElfSize(getSectionForType(DT_STRTAB));
   1.759 +            break;
   1.760 +        case DT_RELSZ:
   1.761 +            dyns[i].value = new ElfSize(getSectionForType(DT_REL));
   1.762 +            break;
   1.763 +        case DT_INIT_ARRAYSZ:
   1.764 +            dyns[i].value = new ElfSize(getSectionForType(DT_INIT_ARRAY));
   1.765 +            break;
   1.766 +        case DT_FINI_ARRAYSZ:
   1.767 +            dyns[i].value = new ElfSize(getSectionForType(DT_FINI_ARRAY));
   1.768 +            break;
   1.769 +        case DT_RELAENT:
   1.770 +            dyns[i].value = new ElfEntSize(getSectionForType(DT_RELA));
   1.771 +            break;
   1.772 +        case DT_SYMENT:
   1.773 +            dyns[i].value = new ElfEntSize(getSectionForType(DT_SYMTAB));
   1.774 +            break;
   1.775 +        case DT_RELENT:
   1.776 +            dyns[i].value = new ElfEntSize(getSectionForType(DT_REL));
   1.777 +            break;
   1.778 +        }
   1.779 +
   1.780 +    file->seekg(pos);
   1.781 +}
   1.782 +
   1.783 +ElfDynamic_Section::~ElfDynamic_Section()
   1.784 +{
   1.785 +    for (unsigned int i = 0; i < shdr.sh_size / shdr.sh_entsize; i++)
   1.786 +        delete dyns[i].value;
   1.787 +}
   1.788 +
   1.789 +void ElfDynamic_Section::serialize(std::ofstream &file, char ei_class, char ei_data)
   1.790 +{
   1.791 +    for (unsigned int i = 0; i < shdr.sh_size / shdr.sh_entsize; i++) {
   1.792 +        Elf_Dyn dyn;
   1.793 +        dyn.d_tag = dyns[i].tag;
   1.794 +        dyn.d_un.d_val = (dyns[i].value != nullptr) ? dyns[i].value->getValue() : 0;
   1.795 +        dyn.serialize(file, ei_class, ei_data);
   1.796 +    }
   1.797 +}
   1.798 +
   1.799 +ElfSymtab_Section::ElfSymtab_Section(Elf_Shdr &s, std::ifstream *file, Elf *parent)
   1.800 +: ElfSection(s, file, parent)
   1.801 +{
   1.802 +    int pos = file->tellg();
   1.803 +    syms.resize(s.sh_size / s.sh_entsize);
   1.804 +    ElfStrtab_Section *strtab = (ElfStrtab_Section *)getLink();
   1.805 +    file->seekg(shdr.sh_offset);
   1.806 +    for (unsigned int i = 0; i < shdr.sh_size / shdr.sh_entsize; i++) {
   1.807 +        Elf_Sym sym(*file, parent->getClass(), parent->getData());
   1.808 +        syms[i].name = strtab->getStr(sym.st_name);
   1.809 +        syms[i].info = sym.st_info;
   1.810 +        syms[i].other = sym.st_other;
   1.811 +        ElfSection *section = (sym.st_shndx == SHN_ABS) ? nullptr : parent->getSection(sym.st_shndx);
   1.812 +        new (&syms[i].value) ElfLocation(section, sym.st_value, ElfLocation::ABSOLUTE);
   1.813 +        syms[i].size = sym.st_size;
   1.814 +        syms[i].defined = (sym.st_shndx != SHN_UNDEF);
   1.815 +    }
   1.816 +    file->seekg(pos);
   1.817 +}
   1.818 +
   1.819 +void
   1.820 +ElfSymtab_Section::serialize(std::ofstream &file, char ei_class, char ei_data)
   1.821 +{
   1.822 +    ElfStrtab_Section *strtab = (ElfStrtab_Section *)getLink();
   1.823 +    for (unsigned int i = 0; i < shdr.sh_size / shdr.sh_entsize; i++) {
   1.824 +        Elf_Sym sym;
   1.825 +        sym.st_name = strtab->getStrIndex(syms[i].name);
   1.826 +        sym.st_info = syms[i].info;
   1.827 +        sym.st_other = syms[i].other;
   1.828 +        sym.st_value = syms[i].value.getValue();
   1.829 +        ElfSection *section = syms[i].value.getSection();
   1.830 +        if (syms[i].defined)
   1.831 +            sym.st_shndx = section ? section->getIndex() : SHN_ABS;
   1.832 +        else
   1.833 +            sym.st_shndx = SHN_UNDEF;
   1.834 +        sym.st_size = syms[i].size;
   1.835 +        sym.serialize(file, ei_class, ei_data);
   1.836 +    }
   1.837 +}
   1.838 +
   1.839 +Elf_SymValue *
   1.840 +ElfSymtab_Section::lookup(const char *name, unsigned int type_filter)
   1.841 +{
   1.842 +    for (std::vector<Elf_SymValue>::iterator sym = syms.begin();
   1.843 +         sym != syms.end(); sym++) {
   1.844 +        if ((type_filter & (1 << ELF32_ST_TYPE(sym->info))) &&
   1.845 +            (strcmp(sym->name, name) == 0)) {
   1.846 +            return &*sym;
   1.847 +        }
   1.848 +    }
   1.849 +    return nullptr;
   1.850 +}
   1.851 +
   1.852 +const char *
   1.853 +ElfStrtab_Section::getStr(unsigned int index)
   1.854 +{
   1.855 +    for (std::vector<table_storage>::iterator t = table.begin();
   1.856 +         t != table.end(); t++) {
   1.857 +        if (index < t->used)
   1.858 +            return t->buf + index;
   1.859 +        index -= t->used;
   1.860 +    }
   1.861 +    assert(1 == 0);
   1.862 +    return nullptr;
   1.863 +}
   1.864 +
   1.865 +const char *
   1.866 +ElfStrtab_Section::getStr(const char *string)
   1.867 +{
   1.868 +    if (string == nullptr)
   1.869 +        return nullptr;
   1.870 +
   1.871 +    // If the given string is within the section, return it
   1.872 +    for (std::vector<table_storage>::iterator t = table.begin();
   1.873 +         t != table.end(); t++)
   1.874 +        if ((string >= t->buf) && (string < t->buf + t->used))
   1.875 +            return string;
   1.876 +
   1.877 +    // TODO: should scan in the section to find an existing string
   1.878 +
   1.879 +    // If not, we need to allocate the string in the section
   1.880 +    size_t len = strlen(string) + 1;
   1.881 +
   1.882 +    if (table.back().size - table.back().used < len)
   1.883 +        table.resize(table.size() + 1);
   1.884 +
   1.885 +    char *alloc_str = table.back().buf + table.back().used;
   1.886 +    memcpy(alloc_str, string, len);
   1.887 +    table.back().used += len;
   1.888 +
   1.889 +    shdr.sh_size += len;
   1.890 +    markDirty();
   1.891 +
   1.892 +    return alloc_str;
   1.893 +}
   1.894 +
   1.895 +unsigned int
   1.896 +ElfStrtab_Section::getStrIndex(const char *string)
   1.897 +{
   1.898 +    if (string == nullptr)
   1.899 +        return 0;
   1.900 +
   1.901 +    unsigned int index = 0;
   1.902 +    string = getStr(string);
   1.903 +    for (std::vector<table_storage>::iterator t = table.begin();
   1.904 +         t != table.end(); t++) {
   1.905 +        if ((string >= t->buf) && (string < t->buf + t->used))
   1.906 +            return index + (string - t->buf);
   1.907 +        index += t->used;
   1.908 +    }
   1.909 +
   1.910 +    assert(1 == 0);
   1.911 +    return 0;
   1.912 +}
   1.913 +
   1.914 +void
   1.915 +ElfStrtab_Section::serialize(std::ofstream &file, char ei_class, char ei_data)
   1.916 +{
   1.917 +    file.seekp(getOffset());
   1.918 +    for (std::vector<table_storage>::iterator t = table.begin();
   1.919 +         t != table.end(); t++)
   1.920 +        file.write(t->buf, t->used);
   1.921 +}

mercurial