co.rsk.asm.EVMDissasembler.java Source code

Java tutorial

Introduction

Here is the source code for co.rsk.asm.EVMDissasembler.java

Source

/*
 * This file is part of RskJ
 * Copyright (C) 2017 RSK Labs Ltd.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package co.rsk.asm;

import co.rsk.lll.asm.CodeBlock;
import co.rsk.lll.asm.EVMAssemblerHelper;
import org.ethereum.util.Utils;
import org.ethereum.vm.DataWord;
import org.ethereum.vm.OpCode;

import java.math.BigInteger;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import static org.apache.commons.lang3.ArrayUtils.getLength;
import static org.apache.commons.lang3.ArrayUtils.isEmpty;
import static org.apache.commons.lang3.ArrayUtils.nullToEmpty;

/**
 * Created by Sergio on 25/10/2016.
 */
public class EVMDissasembler {
    // Does not yet support script versioning bytes
    private byte[] ops;
    private int pc;
    private boolean stopped;
    private byte exeVersion;
    private byte scriptVersion;
    private int startAddr;
    private Set<Integer> jumpdest = new HashSet<>();

    public void stop() {
        stopped = true;
    }

    public DataWord sweepGetDataWord(int n) {
        if (pc + n > ops.length) {
            stop();
            // In this case partial data is copied. At least Ethereumj does this
            // Asummes LSBs are zero. assignDataRange undestands this semantics.
        }

        DataWord dw = new DataWord();
        dw.assignDataRange(ops, pc, n);
        pc += n;
        if (pc >= ops.length)
            stop();

        return dw;
    }

    public static int getScriptVersionInCode(byte[] ops) {
        if (ops.length >= 4) {
            OpCode op = OpCode.code(ops[0]);
            if ((op != null) && op.equals(OpCode.HEADER)) {
                return (ops[2]);
            }
        }
        return 0;
    }

    public int processAndSkipCodeHeader(int i) {
        if (ops.length >= 4) {
            OpCode op = OpCode.code(ops[0]);
            if ((op != null) && op.equals(OpCode.HEADER)) {
                // next byte is executable format version
                // header length in bytes
                exeVersion = ops[1];
                scriptVersion = ops[2];
                byte extHeaderLen = ops[3];
                i += 4 + extHeaderLen;
                startAddr = i;
                pc = i;
            }
        }
        return i;
    }

    public void precompile() {
        int i = 0;
        exeVersion = 0;
        scriptVersion = 0;
        startAddr = 0;
        pc = 0;
        i = processAndSkipCodeHeader(i);
        computeJumpDests(i);

        // Start stopped ?
        if (pc >= ops.length) {
            stop();
        }
    }

    public void computeJumpDests(int i) {
        for (; i < ops.length; ++i) {
            OpCode op = OpCode.code(ops[i]);
            if (op == null)
                continue;

            if (op.equals(OpCode.JUMPDEST))
                jumpdest.add(i);

            if (op.asInt() >= OpCode.PUSH1.asInt() && op.asInt() <= OpCode.PUSH32.asInt()) {
                i += op.asInt() - OpCode.PUSH1.asInt() + 1;
            }
        }
    }

    public static boolean isEthereumCompatibleScript(byte[] aprogramCode) {
        return getScriptVersionInCode(aprogramCode) == 0;
    }

    public byte getOp(int pc) {
        return (getLength(ops) <= pc) ? 0 : ops[pc];
    }

    public byte getCurrentOp() {
        return isEmpty(ops) ? 0 : ops[pc];
    }

    public int getPC() {
        return pc;
    }

    public void setPC(int pc) {
        this.pc = pc;

        if (this.pc >= ops.length) {
            stop();
        }
    }

    public boolean isStopped() {
        return stopped;
    }

    public static String getDissasemble(byte[] code) {
        EVMDissasembler d = new EVMDissasembler();
        return d.dissasemble(code);
    }

    byte[] code;
    CodeBlock block;
    EVMAssemblerHelper helper;
    boolean printOffsets = true;
    StringBuilder sb;
    int[] refIterator;
    int[] tagIterator;

    public String dissasembleCodeBlock(CodeBlock block, EVMAssemblerHelper helper) {
        this.code = block.getCode();
        this.block = block;
        this.helper = helper;
        return dissasembleInternal();

    }

    public String dissasemble(byte[] code) {
        this.code = code;
        return dissasembleInternal();
    }

    public String getVisualLabelName(int labIndex) {
        String ref = helper.getLabelName(labIndex);
        if (ref == null)
            ref = "label_" + Integer.toString(labIndex);
        return ref;
    }

    void printPush(OpCode op) {
        sb.append(' ').append(op.name()).append(' ');
        int nPush = op.val() - OpCode.PUSH1.val() + 1;
        String name = null;
        if ((block != null) && (helper != null) && (nPush == 4)) { // optimization, only find int32 label refs
            int labelId = block.getTagIdByPos(pc + 1, tagIterator);
            if (labelId >= 0) {
                name = getVisualLabelName(labelId);
                sb.append(name);
                if (printOffsets)
                    sb.append(" ;");
            }
        }

        if ((printOffsets) || (name == null)) {
            byte[] data = Arrays.copyOfRange(code, pc + 1, pc + nPush + 1);
            BigInteger bi = new BigInteger(1, data);
            sb.append("0x").append(bi.toString(16));
            if (bi.bitLength() <= 32) {
                sb.append(" (").append(new BigInteger(1, data).toString()).append(") ");
            }
        }
    }

    void printCodeSections() {
        boolean newLine = false;
        if (helper != null) {
            int labIndex = helper.findLabelByPos(pc);
            if (labIndex >= 0) {

                String ref = getVisualLabelName(labIndex);
                newLine = true;
                sb.append(ref + ": ");
            }
        }

        if (block != null) {
            String ref = block.getSourceRefText(pc, refIterator);
            if (ref != null) {
                sb.append("; " + ref.replace('\n', ' '));
                newLine = true;
            }
        }
        if (newLine) {
            sb.append("\n");
            if (printOffsets)
                sb.append(Utils.align("", ' ', 8, false));
        }
    }

    void printOffset() {
        sb.append(Utils.align("" + Integer.toHexString(pc) + ":", ' ', 8, false));
    }

    public String dissasembleInternal() {
        ops = nullToEmpty(code);
        precompile();
        sb = new StringBuilder();
        refIterator = new int[1];
        tagIterator = new int[1];

        while (!isStopped()) {
            if (printOffsets)
                printOffset();

            printCodeSections();
            byte opCode = getOp(pc);
            OpCode op = OpCode.code(opCode);

            if (op == null) {
                sb.append("<UNKNOWN>: ").append(0xFF & opCode).append("\n");
                pc++;
                continue;
            }

            if (op.name().startsWith("PUSH")) {
                printPush(op);
                int nPush = op.val() - OpCode.PUSH1.val() + 1;
                setPC(pc + nPush + 1);
            } else {
                sb.append(' ').append(op.name());
                setPC(pc + 1);
            }
            sb.append('\n');
        }

        return sb.toString();
    }
}