GP-1090 Added SuperH/SuperH4 ELF relocation handler and improved performance of ELF filler segment pruning to avoid uneccessary block creation

This commit is contained in:
ghidra1
2021-07-06 17:12:18 -04:00
parent b3261c0634
commit c2652d36d1
5 changed files with 415 additions and 68 deletions
@@ -34,7 +34,6 @@ import ghidra.app.util.bin.format.elf.extend.ElfLoadAdapter;
import ghidra.app.util.bin.format.elf.relocation.*;
import ghidra.app.util.importer.MessageLog;
import ghidra.framework.options.Options;
import ghidra.framework.store.LockException;
import ghidra.program.database.mem.FileBytes;
import ghidra.program.database.register.AddressRangeObjectMap;
import ghidra.program.model.address.*;
@@ -136,7 +135,7 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper {
resolve(monitor);
if (elf.e_shnum() == 0) {
// create/expand segments to their fullsize if not sections are defined
// create/expand segments to their fullsize if no sections are defined
expandProgramHeaderBlocks(monitor);
}
@@ -146,8 +145,6 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper {
return;
}
pruneDiscardableBlocks();
markupElfHeader(monitor);
markupProgramHeaders(monitor);
markupSectionHeaders(monitor);
@@ -183,10 +180,11 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper {
}
}
private void adjustSegmentAndSectionFileAllocations(ByteProvider byteProvider)
private void adjustSegmentAndSectionFileAllocations(
ByteProvider byteProvider)
throws IOException {
// Identify file ranges not consumed by segments and sections
// Identify file ranges not allocated to segments or sections
RangeMap fileMap = new RangeMap();
fileMap.paintRange(0, byteProvider.length() - 1, -1); // -1: unallocated
@@ -220,17 +218,17 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper {
// Ignore header regions which will always be allocated to blocks
int elfHeaderSize = elf.toDataType().getLength();
fileMap.paintRange(0, elfHeaderSize - 1, -4);
fileMap.paintRange(0, elfHeaderSize - 1, -4); // -4: header block
int programHeaderSize = elf.e_phentsize() * elf.e_phnum();
if (programHeaderSize != 0) {
fileMap.paintRange(elf.e_phoff(), elf.e_phoff() + programHeaderSize - 1, -4);
fileMap.paintRange(elf.e_phoff(), elf.e_phoff() + programHeaderSize - 1, -4); // -4: header block
}
int sectionHeaderSize = elf.e_shentsize() * elf.e_shnum();
if (sectionHeaderSize != 0) {
fileMap.paintRange(elf.e_shoff(), elf.e_shoff() + sectionHeaderSize - 1, -4);
fileMap.paintRange(elf.e_shoff(), elf.e_shoff() + sectionHeaderSize - 1, -4); // -4: header block
}
// Unused file ranges - add as OTHER blocks
// Add unallocated non-zero file regions as OTHER blocks
IndexRangeIterator rangeIterator = fileMap.getIndexRangeIterator(0);
int unallocatedIndex = 0;
while (rangeIterator.hasNext()) {
@@ -239,10 +237,18 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper {
if (value != -1) {
continue;
}
long start = range.getStart();
long length = range.getEnd() - start + 1;
if (isZeroFilledFileRegion(byteProvider, start, length)) {
continue;
}
String name = UNALLOCATED_NAME_PREFIX + unallocatedIndex++;
try {
addInitializedMemorySection(null, range.getStart(),
range.getEnd() - range.getStart() + 1, AddressSpace.OTHER_SPACE.getMinAddress(),
addInitializedMemorySection(null, start, length,
AddressSpace.OTHER_SPACE.getMinAddress(),
name, false, false, false, null, false, false);
}
catch (AddressOverflowException e) {
@@ -251,45 +257,19 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper {
}
}
private void pruneDiscardableBlocks() {
try {
for (MemoryBlock block : memory.getBlocks()) {
long size = block.getSize();
// prune any zero-filled unallocated block or segment blocks smaller than DISCARDABLE_SEGMENT_SIZE
if (!block.getName().startsWith(UNALLOCATED_NAME_PREFIX)) {
// Don't prune segments when sections are absent
if (elf.e_shnum() == 0 || size > DISCARDABLE_SEGMENT_SIZE ||
!block.getName().startsWith(SEGMENT_NAME_PREFIX)) {
continue;
}
}
if (isZeroFilledBlock(block)) {
Msg.debug(this,
"Removing discardable alignment/filler segment at " + block.getStart());
memory.removeBlock(block, TaskMonitor.DUMMY);
}
}
private boolean isZeroFilledFileRegion(ByteProvider byteProvider, long start, long length)
throws IOException {
int bufSize = 16 * 1024;
if (length < bufSize) {
bufSize = (int) length;
}
catch (LockException | MemoryAccessException e) {
throw new AssertException(e); // should never happen
}
}
private boolean isZeroFilledBlock(MemoryBlock block) throws MemoryAccessException {
int bufSize = 8 * 1024;
long blockSize = block.getSize();
if (blockSize < bufSize) {
bufSize = (int) blockSize;
}
byte[] bytes = new byte[bufSize];
Address addr = block.getStart();
long cnt = 0;
while (cnt < blockSize) {
int readLen = block.getBytes(addr.add(cnt), bytes, 0, bufSize);
if (readLen <= 0 || !isZeroedArray(bytes, readLen)) {
long remaining = length;
while (remaining > 0) {
byte[] bytes = byteProvider.readBytes(start, Math.min(remaining, bufSize));
if (!isZeroedArray(bytes, bytes.length)) {
return false;
}
cnt += readLen;
remaining -= bytes.length;
}
return true;
}
@@ -303,6 +283,29 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper {
return true;
}
private boolean isDiscardableFillerSegment(MemoryLoadable loadable, String blockName,
Address start, long fileOffset, long length) throws IOException {
if (elf.e_shnum() == 0 || elf.e_phnum() == 0) {
return false; // only prune if both sections and program headers are present
}
if (length > DISCARDABLE_SEGMENT_SIZE || !blockName.startsWith(SEGMENT_NAME_PREFIX)) {
return false;
}
if (elf.getLoadAdapter().hasFilteredLoadInputStream(this, loadable, start)) {
// block is unable to map directly to file bytes - read from filtered input stream
try (InputStream dataInput =
getInitializedBlockInputStream(loadable, start, fileOffset, length)) {
byte[] bytes = new byte[(int) length];
return dataInput.read(bytes) == bytes.length && isZeroedArray(bytes, bytes.length);
}
}
byte[] bytes = new byte[(int) length];
return fileBytes.getModifiedBytes(fileOffset, bytes) == bytes.length &&
isZeroedArray(bytes, bytes.length);
}
@Override
public MessageLog getLog() {
return log;
@@ -3220,11 +3223,17 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper {
boolean w, boolean x, TaskMonitor monitor)
throws IOException, AddressOverflowException, CancelledException {
long revisedLength = checkBlockLimit(name, dataLength, true);
if (isDiscardableFillerSegment(loadable, name, start, fileOffset, dataLength)) {
Msg.debug(this,
"Discarding " + dataLength + "-byte alignment/filler " + name + " at " + start);
return null;
}
// TODO: MemoryBlockUtil poorly and inconsistently handles duplicate name errors (can throw RuntimeException).
// Are we immune from such errors? If not, how should they be handled?
long revisedLength = checkBlockLimit(name, dataLength, true);
if (start.isNonLoadedMemoryAddress()) {
r = false;
w = false;
@@ -3245,19 +3254,32 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper {
blockComment += " (section truncated)";
}
if (elf.getLoadAdapter().hasFilteredLoadInputStream(this, loadable, start)) {
// block is unable to map directly to file bytes - load from input stream
try (InputStream dataInput =
getInitializedBlockInputStream(loadable, start, fileOffset, revisedLength)) {
return MemoryBlockUtils.createInitializedBlock(program, isOverlay, name, start,
dataInput, revisedLength, blockComment, BLOCK_SOURCE_NAME, r, w, x, log,
monitor);
MemoryBlock block = null;
try {
if (elf.getLoadAdapter().hasFilteredLoadInputStream(this, loadable, start)) {
// block is unable to map directly to file bytes - load from input stream
try (InputStream dataInput =
getInitializedBlockInputStream(loadable, start, fileOffset, revisedLength)) {
block = MemoryBlockUtils.createInitializedBlock(program, isOverlay, name, start,
dataInput, revisedLength, blockComment, BLOCK_SOURCE_NAME, r, w, x, log,
monitor);
}
}
else {
// create block using direct mapping to file bytes
block = MemoryBlockUtils.createInitializedBlock(program, isOverlay, name, start,
fileBytes, fileOffset, revisedLength, blockComment, BLOCK_SOURCE_NAME, r, w, x,
log);
}
}
// create block using direct mapping to file bytes
return MemoryBlockUtils.createInitializedBlock(program, isOverlay, name, start, fileBytes,
fileOffset, revisedLength, blockComment, BLOCK_SOURCE_NAME, r, w, x, log);
finally {
if (block == null) {
Address end = start.addNoWrap(revisedLength - 1);
Msg.error(this, "Unexpected ELF memory bock load conflict when creating '" + name +
"' at " + start.toString(true) + "-" + end.toString(true));
}
}
return block;
}
@Override
@@ -368,18 +368,12 @@ public abstract class MemorySectionResolver {
maxAddr = block.getEnd();
}
else {
// block may be null due to unexpected conflict
// block may be null due to unexpected conflict - allow to continue
block = createInitializedBlock(section.key, false, blockName, address,
fileOffset, rangeSize, section.getComment(), section.isReadable(),
section.isWritable(), section.isExecute(), monitor);
minAddr = address;
maxAddr = address.addNoWrap(rangeSize - 1);
if (block == null) {
// This is a bug but allow load to continue by not referring to block below
Msg.error(this,
"Unexpected ELF memory bock load conflict when creating '" + blockName +
"' at " + minAddr.toString(true) + "-" + maxAddr.toString(true));
}
}
if (fileLoadRangeMap != null) {
long chunkFileOffset = section.getFileOffset() + sectionByteOffset;
@@ -0,0 +1,61 @@
/* ###
* IP: GHIDRA
*
* 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 ghidra.app.util.bin.format.elf.relocation;
import ghidra.app.plugin.core.reloc.RelocationFixupHandler;
import ghidra.app.util.opinion.ElfLoader;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Processor;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.reloc.Relocation;
import ghidra.program.model.util.CodeUnitInsertionException;
public class ElfSH4RelocationFixupHandler extends RelocationFixupHandler {
@Override
public boolean processRelocation(Program program, Relocation relocation, Address oldImageBase,
Address newImageBase) throws MemoryAccessException, CodeUnitInsertionException {
switch (relocation.getType()) {
case SH_ElfRelocationConstants.R_SH_DIR32:
case SH_ElfRelocationConstants.R_SH_REL32:
return process32BitRelocation(program, relocation, oldImageBase, newImageBase);
// case SH_ElfRelocationConstants.R_SH_DIR8WPN:
// case SH_ElfRelocationConstants.R_SH_DIR8WPZ:
// case SH_ElfRelocationConstants.R_SH_IND12W:
// case SH_ElfRelocationConstants.R_SH_DIR8WPL:
}
return false;
}
@Override
public boolean handlesProgram(Program program) {
if (!ElfLoader.ELF_NAME.equals(program.getExecutableFormat())) {
return false;
}
Language language = program.getLanguage();
if (language.getLanguageDescription().getSize() != 32) {
return false;
}
Processor processor = language.getProcessor();
return ("SuperH4".equals(processor.toString()) || "SuperH".equals(processor.toString()));
}
}
@@ -0,0 +1,134 @@
/* ###
* IP: GHIDRA
*
* 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 ghidra.app.util.bin.format.elf.relocation;
public class SH_ElfRelocationConstants {
public static final int R_SH_NONE = 0; // No operation needed
public static final int R_SH_DIR32 = 1; // (S + A) */
public static final int R_SH_REL32 = 2; // (S + A) - P
public static final int R_SH_DIR8WPN = 3; // 8-bit PC relative branch divided by 2 : (((S + A) - P) >> 1) & 0xff
public static final int R_SH_IND12W = 4; // 12-bit PC relative branch divided by 2 : (((S + A) - P) >> 1) & 0xfff
public static final int R_SH_DIR8WPL = 5; // 8-bit PC unsigned-relative branch divided by 4 : (((S + A) - P) >> 2) & 0xff
public static final int R_SH_DIR8WPZ = 6; // 8-bit PC unsigned-relative branch divided by 2 : (((S + A) - P) >> 1) & 0xff
public static final int R_SH_DIR8BP = 7;
public static final int R_SH_DIR8W = 8;
public static final int R_SH_DIR8L = 9;
// Relocation numbering in this file corresponds to GNU binutils and
// values below this point may differ significantly from those specified
// for other uses (e.g., see https://android.googlesource.com/platform/external/elfutils/+/android-4.1.2_r1/libelf/elf.h )
public static final int R_SH_LOOP_START = 10;
public static final int R_SH_LOOP_END = 11;
public static final int R_SH_GNU_VTINHERIT = 22;
public static final int R_SH_GNU_VTENTRY = 23;
public static final int R_SH_SWITCH8 = 24;
public static final int R_SH_SWITCH16 = 25;
public static final int R_SH_SWITCH32 = 26;
public static final int R_SH_USES = 27;
public static final int R_SH_COUNT = 28;
public static final int R_SH_ALIGN = 29;
public static final int R_SH_CODE = 30;
public static final int R_SH_DATA = 31;
public static final int R_SH_LABEL = 32;
public static final int R_SH_DIR16 = 33;
public static final int R_SH_DIR8 = 34;
public static final int R_SH_DIR8UL = 35;
public static final int R_SH_DIR8UW = 36;
public static final int R_SH_DIR8U = 37;
public static final int R_SH_DIR8SW = 38;
public static final int R_SH_DIR8S = 39;
public static final int R_SH_DIR4UL = 40;
public static final int R_SH_DIR4UW = 41;
public static final int R_SH_DIR4U = 42;
public static final int R_SH_PSHA = 43;
public static final int R_SH_PSHL = 44;
public static final int R_SH_DIR5U = 45;
public static final int R_SH_DIR6U = 46;
public static final int R_SH_DIR6S = 47;
public static final int R_SH_DIR10S = 48;
public static final int R_SH_DIR10SW = 49;
public static final int R_SH_DIR10SL = 50;
public static final int R_SH_DIR10SQ = 51;
public static final int R_SH_DIR16S = 53;
public static final int R_SH_TLS_GD_32 = 144;
public static final int R_SH_TLS_LD_32 = 145;
public static final int R_SH_TLS_LDO_32 = 146;
public static final int R_SH_TLS_IE_32 = 147;
public static final int R_SH_TLS_LE_32 = 148;
public static final int R_SH_TLS_DTPMOD32 = 149;
public static final int R_SH_TLS_DTPOFF32 = 150;
public static final int R_SH_TLS_TPOFF32 = 151;
public static final int R_SH_GOT32 = 160;
public static final int R_SH_PLT32 = 161;
public static final int R_SH_COPY = 162;
public static final int R_SH_GLOB_DAT = 163;
public static final int R_SH_JMP_SLOT = 164;
public static final int R_SH_RELATIVE = 165;
public static final int R_SH_GOTOFF = 166;
public static final int R_SH_GOTPC = 167;
public static final int R_SH_GOTPLT32 = 168;
public static final int R_SH_GOT_LOW16 = 169;
public static final int R_SH_GOT_MEDLOW16 = 170;
public static final int R_SH_GOT_MEDHI16 = 171;
public static final int R_SH_GOT_HI16 = 172;
public static final int R_SH_GOTPLT_LOW16 = 173;
public static final int R_SH_GOTPLT_MEDLOW16 = 174;
public static final int R_SH_GOTPLT_MEDHI16 = 175;
public static final int R_SH_GOTPLT_HI16 = 176;
public static final int R_SH_PLT_LOW16 = 177;
public static final int R_SH_PLT_MEDLOW16 = 178;
public static final int R_SH_PLT_MEDHI16 = 179;
public static final int R_SH_PLT_HI16 = 180;
public static final int R_SH_GOTOFF_LOW16 = 181;
public static final int R_SH_GOTOFF_MEDLOW16 = 182;
public static final int R_SH_GOTOFF_MEDHI16 = 183;
public static final int R_SH_GOTOFF_HI16 = 184;
public static final int R_SH_GOTPC_LOW16 = 185;
public static final int R_SH_GOTPC_MEDLOW16 = 186;
public static final int R_SH_GOTPC_MEDHI16 = 187;
public static final int R_SH_GOTPC_HI16 = 188;
public static final int R_SH_GOT10BY4 = 189;
public static final int R_SH_GOTPLT10BY4 = 190;
public static final int R_SH_GOT10BY8 = 191;
public static final int R_SH_GOTPLT10BY8 = 192;
public static final int R_SH_COPY64 = 193;
public static final int R_SH_GLOB_DAT64 = 194;
public static final int R_SH_JMP_SLOT64 = 195;
public static final int R_SH_RELATIVE64 = 196;
public static final int R_SH_SHMEDIA_CODE = 242;
public static final int R_SH_PT_16 = 243;
public static final int R_SH_IMMS16 = 244;
public static final int R_SH_IMMU16 = 245;
public static final int R_SH_IMM_LOW16 = 246;
public static final int R_SH_IMM_LOW16_PCREL = 247;
public static final int R_SH_IMM_MEDLOW16 = 248;
public static final int R_SH_IMM_MEDLOW16_PCREL = 249;
public static final int R_SH_IMM_MEDHI16 = 250;
public static final int R_SH_IMM_MEDHI16_PCREL = 251;
public static final int R_SH_IMM_HI16 = 252;
public static final int R_SH_IMM_HI16_PCREL = 253;
public static final int R_SH_64 = 254;
public static final int R_SH_64_PCREL = 255;
}
@@ -0,0 +1,136 @@
/* ###
* IP: GHIDRA
*
* 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 ghidra.app.util.bin.format.elf.relocation;
import ghidra.app.util.bin.format.elf.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.util.exception.NotFoundException;
public class SH_ElfRelocationHandler extends ElfRelocationHandler {
@Override
public boolean canRelocate(ElfHeader elf) {
return elf.e_machine() == ElfConstants.EM_SH && elf.is32Bit();
}
@Override
public void relocate(ElfRelocationContext elfRelocationContext, ElfRelocation relocation,
Address relocationAddress) throws MemoryAccessException, NotFoundException {
ElfHeader elf = elfRelocationContext.getElfHeader();
if (elf.e_machine() != ElfConstants.EM_SH || !elf.is32Bit()) {
return;
}
Program program = elfRelocationContext.getProgram();
Memory memory = program.getMemory();
int type = relocation.getType();
if (type == SH_ElfRelocationConstants.R_SH_NONE) {
return;
}
int symbolIndex = relocation.getSymbolIndex();
int addend = (int) relocation.getAddend();
ElfSymbol sym = elfRelocationContext.getSymbol(symbolIndex);
String symbolName = sym.getNameAsString();
int offset = (int) relocationAddress.getOffset();
Address symbolAddr = elfRelocationContext.getSymbolAddress(sym);
int symbolValue = (int) elfRelocationContext.getSymbolValue(sym);
int newValue = 0;
switch (type) {
case SH_ElfRelocationConstants.R_SH_DIR32: { //32 bit absolute relocation
if (elfRelocationContext.extractAddend()) {
addend = memory.getInt(relocationAddress);
}
if (addend != 0 && isUnsupportedExternalRelocation(program, relocationAddress,
symbolAddr, symbolName, addend, elfRelocationContext.getLog())) {
addend = 0; // prefer bad fixup for EXTERNAL over really-bad fixup
}
newValue = symbolValue + addend;
memory.setInt(relocationAddress, newValue);
break;
}
case SH_ElfRelocationConstants.R_SH_REL32: { // 32 bit PC relative relocation
if (elfRelocationContext.extractAddend()) {
addend = memory.getInt(relocationAddress);
}
newValue = (symbolValue + addend) - offset;
memory.setInt(relocationAddress, newValue);
break;
}
case SH_ElfRelocationConstants.R_SH_DIR8WPN: // 8-bit PC relative branch divided by 2
case SH_ElfRelocationConstants.R_SH_DIR8WPZ: { // 8-bit PC unsigned-relative branch divided by 2
short oldShortValue = memory.getShort(relocationAddress);
if (elfRelocationContext.extractAddend()) {
addend = oldShortValue & 0xff;
if (type == SH_ElfRelocationConstants.R_SH_DIR8WPN && (addend & 0x80) != 0) {
addend -= 0x100; // sign-extend addend for R_SH_DIR8WPN
}
}
newValue = ((symbolValue + addend) - offset) >> 1;
newValue = (oldShortValue & 0xff00) | (newValue & 0xff);
memory.setShort(relocationAddress, (short) newValue);
break;
}
case SH_ElfRelocationConstants.R_SH_IND12W: { // 12-bit PC relative branch divided by 2
short oldShortValue = memory.getShort(relocationAddress);
if (elfRelocationContext.extractAddend()) {
addend = oldShortValue & 0xfff;
if ((addend & 0x800) != 0) {
addend -= 0x1000; // sign-extend addend
}
}
newValue = ((symbolValue + addend) - offset) >> 1;
newValue = (oldShortValue & 0xf000) | (newValue & 0xfff);
memory.setShort(relocationAddress, (short) newValue);
break;
}
case SH_ElfRelocationConstants.R_SH_DIR8WPL: { // 8-bit PC unsigned-relative branch divided by 4
short oldShortValue = memory.getShort(relocationAddress);
if (elfRelocationContext.extractAddend()) {
addend = oldShortValue & 0xff;
}
newValue = ((symbolValue + addend) - offset) >> 2;
newValue = (oldShortValue & 0xff00) | (newValue & 0xff);
memory.setShort(relocationAddress, (short) newValue);
break;
}
case SH_ElfRelocationConstants.R_SH_COPY: {
markAsWarning(program, relocationAddress, "R_SH_COPY", symbolName, symbolIndex,
"Runtime copy not supported", elfRelocationContext.getLog());
break;
}
default: {
markAsUnhandled(program, relocationAddress, type, symbolIndex, symbolName,
elfRelocationContext.getLog());
break;
}
}
}
}