| /* |
| * 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); |
| } |
| } |