| /* | |
| * Copyright 2016 WebAssembly Community Group participants | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #include "support/archive.h" | |
| #include "support/utilities.h" | |
| #include <cstring> | |
| static const char* const magic = "!<arch>\n"; | |
| class ArchiveMemberHeader { | |
| public: | |
| uint8_t fileName[16]; | |
| uint8_t timestamp[12]; | |
| uint8_t UID[6]; | |
| uint8_t GID[6]; | |
| uint8_t accessMode[8]; | |
| uint8_t size[10]; // Size of data only, not including padding or header | |
| uint8_t magic[2]; | |
| std::string getName() const; | |
| // Members are not larger than 4GB | |
| uint32_t getSize() const; | |
| }; | |
| std::string ArchiveMemberHeader::getName() const { | |
| char endChar; | |
| if (fileName[0] == '/') { | |
| // Special name (string table or reference, or symbol table) | |
| endChar = ' '; | |
| } else { | |
| endChar = '/'; // regular name | |
| } | |
| auto* end = | |
| static_cast<const uint8_t*>(memchr(fileName, endChar, sizeof(fileName))); | |
| if (!end) { | |
| end = fileName + sizeof(fileName); | |
| } | |
| return std::string((char*)(fileName), end - fileName); | |
| } | |
| uint32_t ArchiveMemberHeader::getSize() const { | |
| auto* end = static_cast<const char*>(memchr(size, ' ', sizeof(size))); | |
| std::string sizeString((const char*)size, end); | |
| auto sizeInt = std::stoll(sizeString, nullptr, 10); | |
| if (sizeInt < 0 || sizeInt >= std::numeric_limits<uint32_t>::max()) { | |
| wasm::Fatal() << "Malformed archive: size parsing failed\n"; | |
| } | |
| return static_cast<uint32_t>(sizeInt); | |
| } | |
| Archive::Archive(Buffer& b, bool& error) | |
| : data(b), symbolTable({nullptr, 0}), stringTable({nullptr, 0}), | |
| firstRegularData(nullptr) { | |
| error = false; | |
| if (data.size() < strlen(magic) || | |
| memcmp(data.data(), magic, strlen(magic))) { | |
| error = true; | |
| return; | |
| } | |
| // We require GNU format archives. So the first member may be named "/" and it | |
| // points to the symbol table. The next member may optionally be "//" and | |
| // point to a string table if a filename is too large to fit in the 16-char | |
| // name field of the header. | |
| child_iterator it = child_begin(false); | |
| if (it.hasError()) { | |
| error = true; | |
| return; | |
| } | |
| child_iterator end = child_end(); | |
| if (it == end) { | |
| return; // Empty archive. | |
| } | |
| const Child* c = &*it; | |
| auto increment = [&]() { | |
| ++it; | |
| error = it.hasError(); | |
| if (error) { | |
| return true; | |
| } | |
| c = &*it; | |
| return false; | |
| }; | |
| std::string name = c->getRawName(); | |
| if (name == "/") { | |
| symbolTable = c->getBuffer(); | |
| if (increment() || it == end) { | |
| return; | |
| } | |
| name = c->getRawName(); | |
| } | |
| if (name == "//") { | |
| stringTable = c->getBuffer(); | |
| if (increment() || it == end) { | |
| return; | |
| } | |
| setFirstRegular(*c); | |
| return; | |
| } | |
| if (name[0] != '/') { | |
| setFirstRegular(*c); | |
| return; | |
| } | |
| // Not a GNU archive. | |
| error = true; | |
| } | |
| Archive::Child::Child(const Archive* parent, const uint8_t* data, bool* error) | |
| : parent(parent), data(data) { | |
| if (!data) { | |
| return; | |
| } | |
| len = sizeof(ArchiveMemberHeader) + getHeader()->getSize(); | |
| startOfFile = sizeof(ArchiveMemberHeader); | |
| } | |
| uint32_t Archive::Child::getSize() const { return len - startOfFile; } | |
| Archive::SubBuffer Archive::Child::getBuffer() const { | |
| return {data + startOfFile, getSize()}; | |
| } | |
| std::string Archive::Child::getRawName() const { | |
| return getHeader()->getName(); | |
| } | |
| Archive::Child Archive::Child::getNext(bool& error) const { | |
| // Members are aligned to even byte boundaries. | |
| uint32_t nextOffset = len + (len & 1); | |
| if ((size_t)(data - (const uint8_t*)parent->data.data() + nextOffset) >= | |
| parent->data.size()) { // End of the archive. | |
| return Child(); | |
| } | |
| return Child(parent, data + nextOffset, &error); | |
| } | |
| std::string Archive::Child::getName() const { | |
| std::string name = getRawName(); | |
| // Check if it's a special name. | |
| if (name[0] == '/') { | |
| if (name.size() == 1) { // Linker member. | |
| return name; | |
| } | |
| if (name.size() == 2 && name[1] == '/') { // String table. | |
| return name; | |
| } | |
| // It's a long name. | |
| // Get the offset. | |
| int offset = std::stoi(name.substr(1), nullptr, 10); | |
| // Verify it. | |
| if (offset < 0 || (unsigned)offset >= parent->stringTable.len) { | |
| wasm::Fatal() << "Malformed archive: name parsing failed\n"; | |
| } | |
| std::string addr(parent->stringTable.data + offset, | |
| parent->stringTable.data + parent->stringTable.len); | |
| // GNU long file names end with a "/\n". | |
| size_t end = addr.find('\n'); | |
| return addr.substr(0, end - 1); | |
| } | |
| // It's a simple name. | |
| if (name[name.size() - 1] == '/') { | |
| return name.substr(0, name.size() - 1); | |
| } | |
| return name; | |
| } | |
| Archive::child_iterator Archive::child_begin(bool SkipInternal) const { | |
| if (data.size() == 0) { | |
| return child_end(); | |
| } | |
| if (SkipInternal) { | |
| child_iterator it; | |
| it.child = Child(this, firstRegularData, &it.error); | |
| return it; | |
| } | |
| auto* loc = (const uint8_t*)data.data() + strlen(magic); | |
| child_iterator it; | |
| it.child = Child(this, loc, &it.error); | |
| return it; | |
| } | |
| Archive::child_iterator Archive::child_end() const { return Child(); } | |
| namespace { | |
| struct Symbol { | |
| uint32_t symbolIndex; | |
| uint32_t stringIndex; | |
| void next(Archive::SubBuffer& symbolTable) { | |
| // Symbol table entries are NUL-terminated. Skip past the next NUL. | |
| stringIndex = strchr((char*)symbolTable.data + stringIndex, '\0') - | |
| (char*)symbolTable.data + 1; | |
| ++symbolIndex; | |
| } | |
| }; | |
| } // namespace | |
| static uint32_t read32be(const uint8_t* buf) { | |
| return static_cast<uint32_t>(buf[0]) << 24 | | |
| static_cast<uint32_t>(buf[1]) << 16 | | |
| static_cast<uint32_t>(buf[2]) << 8 | static_cast<uint32_t>(buf[3]); | |
| } | |
| void Archive::dump() const { | |
| printf("Archive data %p len %zu, firstRegularData %p\n", | |
| data.data(), | |
| data.size(), | |
| firstRegularData); | |
| printf("Symbol table %p, len %u\n", symbolTable.data, symbolTable.len); | |
| printf("string table %p, len %u\n", stringTable.data, stringTable.len); | |
| const uint8_t* buf = symbolTable.data; | |
| if (!buf) { | |
| for (auto c = child_begin(), e = child_end(); c != e; ++c) { | |
| printf("Child %p, len %u, name %s, size %u\n", | |
| c->data, | |
| c->len, | |
| c->getName().c_str(), | |
| c->getSize()); | |
| } | |
| return; | |
| } | |
| uint32_t symbolCount = read32be(buf); | |
| printf("Symbol count %u\n", symbolCount); | |
| buf += sizeof(uint32_t) + (symbolCount * sizeof(uint32_t)); | |
| uint32_t string_start_offset = buf - symbolTable.data; | |
| Symbol sym = {0, string_start_offset}; | |
| while (sym.symbolIndex != symbolCount) { | |
| printf("Symbol %u, offset %u\n", sym.symbolIndex, sym.stringIndex); | |
| // get the member | |
| uint32_t offset = read32be(symbolTable.data + sym.symbolIndex * 4); | |
| auto* loc = (const uint8_t*)&data[offset]; | |
| child_iterator it; | |
| it.child = Child(this, loc, &it.error); | |
| printf("Child %p, len %u\n", it.child.data, it.child.len); | |
| } | |
| } |