blob: 17c1cc25fe5d71d84f6c57221c1ff67e428a844b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2017 Google, Inc and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Stefan Xenos (Google) - Initial implementation
*******************************************************************************/
package org.aspectj.org.eclipse.jdt.internal.core.nd.db;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Records a record of every modification to the database, in a circular buffer of fixed size. Whenever anything writes
* to the database, the log records the address and size of the write, along with a call stack describing what was going
* on at the time of the write. The actual bytes written to the database are not recorded. In addition to writes, it
* also records every invocation of malloc and free.
* <p>
* Given a memory address range, we can trace the log backwards to find everything that ever happened to that address
* range since the start of the log.
* </p>
* "call stacks" don't use java call stacks. They use explicit tags that are pushed and popped at the start and
* end of operations related to modifying the database.
*/
public class ModificationLog {
/**
* Used to attach messages to events in the log. Tags should be allocated in static initializers at application
* startup by calling {@link ModificationLog#createTag(String)}. Once allocated, the tag can be pushed and popped on to
* the stack in the log to mark the beginning and end of operations.
*/
public static class Tag {
public final String name;
public final int opNum;
Tag(String name, int opNum) {
this.name = name;
this.opNum = opNum;
}
@Override
public String toString() {
return this.opNum + ":" + this.name; //$NON-NLS-1$
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + this.opNum;
return result;
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Tag other = (Tag) obj;
if (this.opNum != other.opNum)
return false;
return true;
}
}
/**
* Represents a single entry in a {@link MemoryAccessLog}. That is, a single read, write, malloc, or free event that
* affected the memory range of interest.
*/
public static class MemoryOperation {
private final List<Tag> stack;
private final long time;
private final long startAddress;
private final int addressSize;
private final byte operationType;
public MemoryOperation(byte operationType, long time, long startAddress, int size, List<Tag> stack) {
super();
this.operationType = operationType;
this.time = time;
this.startAddress = startAddress;
this.addressSize = size;
this.stack = stack;
}
public List<Tag> getStack() {
return this.stack;
}
public long getTime() {
return this.time;
}
public long getStartAddress() {
return this.startAddress;
}
public int getSize() {
return this.addressSize;
}
public byte getOperationType() {
return this.operationType;
}
public void printTo(StringBuilder builder, int indent) {
indent(builder, indent);
switch (getOperationType()) {
case FREE_OPERATION: builder.append("freed"); break; //$NON-NLS-1$
case MALLOC_OPERATION: builder.append("malloc'd"); break; //$NON-NLS-1$
case WRITE_OPERATION: builder.append("wrote"); break; //$NON-NLS-1$
}
builder.append(" [address "); //$NON-NLS-1$
builder.append(this.startAddress);
builder.append(", size "); //$NON-NLS-1$
builder.append(this.addressSize);
builder.append("] at time "); //$NON-NLS-1$
builder.append(this.time);
builder.append("\n"); //$NON-NLS-1$
List<Tag> theStack = new ArrayList<>();
theStack.addAll(getStack());
Collections.reverse(theStack);
for (Tag next : theStack) {
indent(builder, indent + 1);
builder.append(next.name + "\n"); //$NON-NLS-1$
}
}
}
/**
* Contains a log of events related to a specific range of database addresses, in reverse chronological order.
*/
public static class MemoryAccessLog {
private final List<MemoryOperation> operations;
public MemoryAccessLog(List<MemoryOperation> operations) {
super();
this.operations = operations;
}
/**
* Returns a list of operations, in reverse order of time.
*/
public List<MemoryOperation> getOperations() {
return this.operations;
}
/**
* Returns true iff this log contains a double malloc or a double free
*/
public boolean hasInconsistentMemoryAllocation() {
boolean known = false;
boolean allocated = false;
for (MemoryOperation next : this.operations) {
boolean newAllocatedState;
if (next.getOperationType() == MALLOC_OPERATION) {
newAllocatedState = false;
} else if (next.getOperationType() == FREE_OPERATION) {
newAllocatedState = true;
} else {
continue;
}
if (!known) {
known = true;
} else if (allocated == newAllocatedState) {
return true;
}
allocated = newAllocatedState;
}
return false;
}
/**
* Search for anomalies in the log and produce a reduced report
*
* @return a log containing the most interesting results
*/
public MemoryAccessLog reduce(int maxWrites) {
boolean includeAllMallocs = hasInconsistentMemoryAllocation();
int numWrites = 0;
List<MemoryOperation> result = new ArrayList<>();
for (MemoryOperation next : this.operations) {
boolean keepGoing = true;
switch (next.getOperationType()) {
case MALLOC_OPERATION: {
result.add(next);
keepGoing = includeAllMallocs;
break;
}
case FREE_OPERATION: {
result.add(next);
break;
}
case WRITE_OPERATION: {
if (numWrites < maxWrites) {
result.add(next);
}
numWrites++;
}
}
if (!keepGoing) {
break;
}
}
return new MemoryAccessLog(result);
}
}
private static Map<Integer, Tag> activeTags = new HashMap<>();
private final ArrayDeque<Tag> operationStack = new ArrayDeque<>();
private long[] buffer0;
private int[] buffer1;
private byte[] operation;
private int insertionPosition;
private int currentEntries;
private long timer;
public static final byte PUSH_OPERATION = 0;
public static final byte POP_OPERATION = 1;
public static final byte WRITE_OPERATION = 2;
public static final byte MALLOC_OPERATION = 3;
public static final byte FREE_OPERATION = 4;
public ModificationLog(int size) {
allocateBuffers(size);
}
public void clear() {
this.currentEntries = 0;
}
private void allocateBuffers(int sizeInMegs) {
int entries = getBufferEntriesFor(sizeInMegs);
if (entries != 0) {
this.buffer0 = new long[entries];
this.buffer1 = new int[entries];
this.operation = new byte[entries];
} else {
this.buffer0 = null;
this.buffer1 = null;
this.operation = null;
}
}
private static int getBufferEntriesFor(int sizeInMegs) {
long sizeOfABufferEntry = 8 + 4 + 1; // size, in bytes, of one long, one int, and one byte
return (int) (sizeInMegs * 1024L * 1024L / sizeOfABufferEntry);
}
public int getBufferEntries() {
return this.buffer0 == null ? 0 : this.buffer0.length;
}
public void setBufferSize(int megs) {
int oldBufferLength = getBufferEntries();
int newBufferLength = getBufferEntriesFor(megs);
if (oldBufferLength == newBufferLength) {
return;
}
long[] oldBuffer0 = this.buffer0;
int[] oldBuffer1 = this.buffer1;
byte[] oldOperation = this.operation;
allocateBuffers(megs);
if (this.buffer0 == null) {
this.currentEntries = 0;
this.insertionPosition = 0;
this.operationStack.clear();
return;
}
int newBufferSize = Math.min(this.buffer0.length, this.currentEntries);
if (oldBufferLength > 0) {
int readStart = (this.insertionPosition + oldBufferLength - newBufferSize) % oldBufferLength;
if (readStart >= this.insertionPosition) {
int entriesFromEnd = oldBufferLength - readStart;
System.arraycopy(oldBuffer0, readStart, this.buffer0, 0, entriesFromEnd);
System.arraycopy(oldBuffer1, readStart, this.buffer1, 0, entriesFromEnd);
System.arraycopy(oldOperation, readStart, this.operation, 0, entriesFromEnd);
System.arraycopy(oldBuffer0, 0, this.buffer0, entriesFromEnd, this.insertionPosition);
System.arraycopy(oldBuffer1, 0, this.buffer1, entriesFromEnd, this.insertionPosition);
System.arraycopy(oldOperation, 0, this.operation, entriesFromEnd, this.insertionPosition);
} else {
int entriesToCopy = this.insertionPosition - readStart;
System.arraycopy(oldBuffer0, readStart, this.buffer0, 0, entriesToCopy);
System.arraycopy(oldBuffer1, readStart, this.buffer1, 0, entriesToCopy);
System.arraycopy(oldOperation, readStart, this.operation, 0, entriesToCopy);
}
}
this.currentEntries = newBufferSize;
this.insertionPosition = newBufferSize % this.buffer0.length;
}
public static void indent(StringBuilder builder, int indent) {
for (int count = 0; count < indent; count++) {
builder.append(" "); //$NON-NLS-1$
}
}
public boolean enabled() {
return this.buffer0 != null;
}
public void start(Tag tag) {
if (!enabled()) {
return;
}
this.operationStack.add(tag);
addToQueue(PUSH_OPERATION, 0, tag.opNum);
}
public void end(Tag tag) {
if (!enabled()) {
return;
}
if (!this.operationStack.getLast().equals(tag)) {
throw new IllegalStateException();
}
this.operationStack.removeLast();
addToQueue(POP_OPERATION, 0, tag.opNum);
}
public void recordWrite(long address, int size) {
if (!enabled()) {
return;
}
this.timer++;
addToQueue(WRITE_OPERATION, address, size);
}
public void recordMalloc(long address, int size) {
if (!enabled()) {
return;
}
this.timer++;
addToQueue(MALLOC_OPERATION, address, size);
}
public void recordFree(long address, int size) {
if (!enabled()) {
return;
}
this.timer++;
addToQueue(FREE_OPERATION, address, size);
}
private void addToQueue(byte opConstant, long arg0, int arg1) {
this.buffer0[this.insertionPosition] = arg0;
this.buffer1[this.insertionPosition] = arg1;
this.operation[this.insertionPosition] = opConstant;
this.insertionPosition = (this.insertionPosition + 1) % this.buffer0.length;
if (this.currentEntries < this.buffer0.length) {
this.currentEntries++;
}
}
// prints count number of last log records to stdout
// may be useful when troubleshooting heap corruption
public void printLog(int count) {
for (int i = 0; i < count; i++) {
int pos = (this.insertionPosition - count + i) % this.buffer0.length;
byte opcode = this.operation[pos];
switch (opcode) {
case FREE_OPERATION:
System.out.printf("FREE_OPERATION(address=%x, size=%d)\n", this.buffer0[pos], this.buffer1[pos]); //$NON-NLS-1$
break;
case MALLOC_OPERATION:
System.out.printf("MALLOC_OPERATION(address=%x, size=%d)\n", this.buffer0[pos], this.buffer1[pos]); //$NON-NLS-1$
break;
case WRITE_OPERATION:
System.out.printf("WRITE_OPERATION(address=%x, size=%d)\n", this.buffer0[pos], this.buffer1[pos]); //$NON-NLS-1$
break;
case PUSH_OPERATION:
System.out.printf("PUSH_OPERATION(tag=%s)\n", activeTags.get(this.buffer1[pos])); //$NON-NLS-1$
break;
case POP_OPERATION:
System.out.printf("POP_OPERATION(tag=%s)\n", activeTags.get(this.buffer1[pos])); //$NON-NLS-1$
break;
default:
System.out.printf("UNKNOWN(opcode=%d, arg0=%d, arg1=%d)\n", opcode, this.buffer0[pos], this.buffer1[pos]); //$NON-NLS-1$
break;
}
}
}
public long getWriteCount() {
return this.timer;
}
/**
* Returns information about the last write to the given address range
*/
public MemoryAccessLog getReportFor(long address, int size) {
List<Tag> tags = new ArrayList<>();
tags.addAll(this.operationStack);
List<MemoryOperation> operations = new ArrayList<>();
if (this.buffer0 != null) {
int pointerToStart = (this.insertionPosition + this.buffer0.length - this.currentEntries) % this.buffer0.length;
int currentPosition = (this.insertionPosition + this.buffer0.length - 1) % this.buffer0.length;
long currentWrite = this.timer;
do {
long nextAddress = this.buffer0[currentPosition];
int nextArgument = this.buffer1[currentPosition];
byte nextOp = this.operation[currentPosition];
switch (nextOp) {
case POP_OPERATION: {
tags.add(getTagForId(nextArgument));
break;
}
case PUSH_OPERATION: {
tags.remove(tags.size() - 1);
break;
}
default: {
boolean isMatch = false;
if (address < nextAddress) {
long diff = nextAddress - address;
if (diff < size) {
isMatch = true;
}
} else {
long diff = address - nextAddress;
if (diff < nextArgument) {
isMatch = true;
}
}
if (isMatch) {
List<Tag> stack = new ArrayList<>();
stack.addAll(tags);
MemoryOperation nextOperation = new MemoryOperation(nextOp, currentWrite, nextAddress,
nextArgument, stack);
operations.add(nextOperation);
}
currentWrite--;
}
}
currentPosition = (currentPosition + this.buffer0.length - 1) % this.buffer0.length;
} while (currentPosition != pointerToStart);
}
return new MemoryAccessLog(operations);
}
public static Tag createTag(String tagName) {
Tag result = new Tag(tagName, activeTags.size());
activeTags.put(activeTags.size(), result);
return result;
}
private Tag getTagForId(int nextArgument) {
return activeTags.get(nextArgument);
}
}