blob: 883b2f7dfda249940fd7e083ebfa7895e9557641 [file] [log] [blame]
// Copyright 2016 The Tulsi Authors. All rights reserved.
//
// 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 "mach_o_container.h"
#include <assert.h>
#include <sys/stat.h>
#include <mach-o/fat.h>
#include <mach-o/loader.h>
#include <mach-o/swap.h>
#include "mach_o_file.h"
namespace post_processor {
MachOContainer::MachOContainer(const std::string &filename, bool verbose) :
filename_(filename),
file_(nullptr),
verbose_(verbose),
content_32_(nullptr),
content_64_(nullptr),
host_byte_order_(NXHostByteOrder()) {
}
MachOContainer::~MachOContainer() {
if (file_) {
fclose(file_);
}
}
namespace {
off_t GetFileSize(FILE *file) {
struct stat file_stat;
int fd = fileno(file);
if (fstat(fd, &file_stat)) {
fprintf(stderr, "Failed to retrieve file size.\n");
return 0;
}
return file_stat.st_size;
}
} // namespace
ReturnCode MachOContainer::Read() {
if (file_) {
fclose(file_);
}
file_ = fopen(filename_.c_str(), "rb+");
if (!file_) {
return ERR_OPEN_FAILED;
}
bool swap_byte_ordering;
FileFormat file_format;
ReturnCode retval = PeekMagicHeader(&file_format, &swap_byte_ordering);
if (retval != ERR_OK) {
return retval;
}
switch(file_format) {
case FF_32: {
off_t content_size = GetFileSize(file_);
return Read32BitContainer(swap_byte_ordering, content_size);
}
case FF_64: {
off_t content_size = GetFileSize(file_);
return Read64BitContainer(swap_byte_ordering, content_size);
}
case FF_FAT:
return ReadFatContainer(swap_byte_ordering);
case FF_INVALID:
return ERR_INVALID_FILE;
}
}
ReturnCode MachOContainer::PerformDeferredWrites() {
bool content_32_rewrite_needed =
Has32Bit() && content_32_->has_deferred_writes();
bool content_64_rewrite_needed =
Has64Bit() && content_64_->has_deferred_writes();
// TODO(abaire): Support full container rewrite.
// In practice this is not required for Tulsi since it does not support the
// generation of true fat binaries (Bazel always produces a fat container but
// only one Mach-O file will be present therein).
// Since each Mach-O file returns its entire data map and the various
// architectures are completely independent, this should be straightforward to
// implement and is omitted for the sake of maintenance/test reduction.
if (content_32_rewrite_needed && content_64_rewrite_needed) {
fprintf(stderr, "fat binaries are not yet supported.\n");
return ERR_NOT_IMPLEMENTED;
}
// TODO(abaire): If this is a single-archive fat container, patch the size
// member of the appropriate fat_arch member.
if (content_32_rewrite_needed) {
std::vector<uint8_t> data_32;
ReturnCode retval = content_32_->SerializeWithDeferredWrites(&data_32);
if (retval) {
return retval;
}
fseeko(file_, content_32_->content_offset(), SEEK_SET);
fwrite(data_32.data(), 1, data_32.size(), file_);
}
if (content_64_rewrite_needed) {
std::vector<uint8_t> data_64;
ReturnCode retval = content_64_->SerializeWithDeferredWrites(&data_64);
if (retval) {
return retval;
}
fseeko(file_, content_64_->content_offset(), SEEK_SET);
fwrite(data_64.data(), 1, data_64.size(), file_);
}
return ERR_OK;
}
ReturnCode MachOContainer::PeekMagicHeader(FileFormat *fileFormat,
bool *swap) {
assert(fileFormat && swap);
*fileFormat = FF_INVALID;
*swap = false;
uint32_t magic_header;
size_t magic_header_size = sizeof(magic_header);
if (fread(&magic_header, magic_header_size, 1, file_) != 1) {
fprintf(stderr, "Failed to read magic header.\n");
return ERR_READ_FAILED;
}
fseeko(file_, -magic_header_size, SEEK_CUR);
switch (magic_header) {
case MH_CIGAM:
*swap = true;
case MH_MAGIC:
*fileFormat = FF_32;
break;
case MH_CIGAM_64:
*swap = true;
case MH_MAGIC_64:
*fileFormat = FF_64;
break;
case FAT_CIGAM:
*swap = true;
case FAT_MAGIC:
*fileFormat = FF_FAT;
break;
default:
fprintf(stderr, "Invalid magic header value 0x%X.\n", magic_header);
return ERR_INVALID_FILE;
}
return ERR_OK;
}
ReturnCode MachOContainer::ReadFatContainer(bool swap_byte_ordering) {
fat_container_header.reset(new fat_header);
if (fread(fat_container_header.get(),
sizeof(fat_header),
1,
file_) != 1) {
fprintf(stderr, "Failed to read fat header.\n");
return ERR_READ_FAILED;
}
if (swap_byte_ordering) {
swap_fat_header(fat_container_header.get(), host_byte_order_);
}
fat_archs.reset(new fat_arch[fat_container_header->nfat_arch]);
if (!fat_archs.get()) {
fprintf(stderr,
"Failed to allocate %d fat headers.\n",
fat_container_header->nfat_arch);
return ERR_OUT_OF_MEMORY;
}
if (fread(fat_archs.get(),
sizeof(fat_archs[0]),
fat_container_header->nfat_arch,
file_) != fat_container_header->nfat_arch) {
fprintf(stderr,
"Failed to read %d fat headers.\n",
fat_container_header->nfat_arch);
return ERR_READ_FAILED;
}
if (swap_byte_ordering) {
swap_fat_arch(fat_archs.get(),
fat_container_header->nfat_arch,
host_byte_order_);
}
for (auto i = 0; i < fat_container_header->nfat_arch; ++i) {
const fat_arch &arch_info = fat_archs[i];
fseeko(file_, arch_info.offset, SEEK_SET);
FileFormat format;
bool swap;
ReturnCode retval = PeekMagicHeader(&format, &swap);
if (retval != ERR_OK) {
return retval;
}
if (format == FF_32) {
retval = Read32BitContainer(swap, arch_info.size);
if (retval != ERR_OK) {
return retval;
}
} else if (format == FF_64) {
retval = Read64BitContainer(swap, arch_info.size);
if (retval != ERR_OK) {
return retval;
}
} else {
fprintf(stderr,
"Unexpectedly found nested file type %d in fat arch section.\n",
format);
return ERR_INVALID_FILE;
}
}
return ERR_OK;
}
ReturnCode MachOContainer::Read32BitContainer(bool swap_byte_ordering,
off_t content_size) {
off_t content_offset = ftello(file_);
content_32_.reset(new MachOFile32(filename_,
content_offset,
content_size,
swap_byte_ordering,
verbose_));
return content_32_->Read();
}
ReturnCode MachOContainer::Read64BitContainer(bool swap_byte_ordering,
off_t content_size) {
off_t content_offset = ftello(file_);
content_64_.reset(new MachOFile64(filename_,
content_offset,
content_size,
swap_byte_ordering,
verbose_));
return content_64_->Read();
}
} // namespace post_processor