/*
* Copyright 2016-present Facebook, Inc.
*
* 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.
*/
package com.facebook.buck.macho;
import com.facebook.buck.charset.NulTerminatedCharsetDecoder;
import com.facebook.buck.log.Logger;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
public class CompDirReplacer {
private static final Logger LOG = Logger.get(CompDirReplacer.class);
private final ByteBuffer buffer;
private final NulTerminatedCharsetDecoder nulTerminatedCharsetDecoder;
public static void replaceCompDirInFile(
Path path, String oldCompDir, String newCompDir, NulTerminatedCharsetDecoder decoder)
throws IOException {
try (FileChannel file =
FileChannel.open(path, StandardOpenOption.READ, StandardOpenOption.WRITE)) {
ByteBuffer byteBuffer = file.map(FileChannel.MapMode.READ_WRITE, 0, file.size());
CompDirReplacer compDirReplacer = new CompDirReplacer(byteBuffer, decoder);
compDirReplacer.replaceCompDir(oldCompDir, newCompDir);
}
}
public CompDirReplacer(
ByteBuffer byteBuffer, NulTerminatedCharsetDecoder nulTerminatedCharsetDecoder) {
this.buffer = byteBuffer;
this.nulTerminatedCharsetDecoder = nulTerminatedCharsetDecoder;
}
private void processThinBinary(
final MachoMagicInfo magicInfo, final String oldCompDir, final String updatedCompDir) {
buffer.position(0);
ImmutableList<SegmentCommand> segmentCommands =
LoadCommandUtils.findLoadCommandsWithClass(
buffer, nulTerminatedCharsetDecoder, SegmentCommand.class);
Preconditions.checkArgument(
segmentCommands.size() == 1,
"Found %d SegmentCommands, expected 1",
segmentCommands.size());
processSectionsInSegmentCommand(segmentCommands.get(0), magicInfo, oldCompDir, updatedCompDir);
}
private void processSectionsInSegmentCommand(
SegmentCommand segmentCommand,
MachoMagicInfo magicInfo,
final String oldCompDir,
final String updatedCompDir) {
try {
SegmentCommandUtils.enumerateSectionsInSegmentLoadCommand(
buffer,
magicInfo,
segmentCommand,
nulTerminatedCharsetDecoder,
input -> updateCompDirInSection(input, oldCompDir, updatedCompDir));
} catch (IOException e) {
LOG.error(e, "Unable to process __DWARF.__debug_str section");
}
}
private Boolean updateCompDirInSection(
Section section, String oldCompDir, String updatedCompDir) {
if (section.getSegname().equals(CommandSegmentSectionNames.SEGMENT_NAME_DWARF)
&& section.getSectname().equals(CommandSegmentSectionNames.SECTION_NAME_DEBUG_STR)) {
findAndUpdateCompDirInDebugSection(section, oldCompDir, updatedCompDir);
return false;
}
return true;
}
private void findAndUpdateCompDirInDebugSection(
Section section, String oldCompDir, String updatedCompDir) {
final long maximumValidOffset = section.getOffset().longValue() + section.getSize().longValue();
int offset = section.getOffset().intValue();
while (offset < maximumValidOffset) {
buffer.position(offset);
String string;
try {
string = nulTerminatedCharsetDecoder.decodeString(buffer);
} catch (CharacterCodingException e) {
LOG.error(e, "Unable to read read string from debug string table, offset %d", offset);
break;
}
if (string.equals(oldCompDir)) {
LOG.verbose("Found comp dir at %d, overwriting it with %s", offset, updatedCompDir);
buffer.position(offset);
buffer.put(updatedCompDir.getBytes(StandardCharsets.UTF_8));
buffer.put((byte) 0x00);
break;
}
offset += SymTabCommandUtils.sizeOfStringTableEntryWithContents(string);
}
}
public void replaceCompDir(String oldCompDir, String updatedCompDir) throws IOException {
Preconditions.checkArgument(
oldCompDir.length() >= updatedCompDir.length(),
"Updated compdir length must be less or equal to old compdir length as replace is "
+ "performed in place");
Preconditions.checkArgument(
!oldCompDir.equals(updatedCompDir), "Updated compdir must be different from old compdir");
MachoMagicInfo magicInfo = MachoMagicInfoUtils.getMachMagicInfo(buffer);
if (!magicInfo.isValidMachMagic()) {
throw new IOException("Cannot locate magic for Mach O binary.");
}
if (magicInfo.isFatBinaryHeaderMagic()) {
throw new IOException("Fat binaries are not supported at this level.");
}
buffer.order(magicInfo.isSwapped() ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
processThinBinary(magicInfo, oldCompDir, updatedCompDir);
}
}