Package com.litecoding.smali2java.renderer

Source Code of com.litecoding.smali2java.renderer.SmaliRenderer$SmaliBlock

package com.litecoding.smali2java.renderer;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import com.litecoding.smali2java.entity.smali.ClassRef;
import com.litecoding.smali2java.entity.smali.FieldRef;
import com.litecoding.smali2java.entity.smali.Instruction;
import com.litecoding.smali2java.entity.smali.Label;
import com.litecoding.smali2java.entity.smali.OpcodeData;
import com.litecoding.smali2java.entity.smali.Register;
import com.litecoding.smali2java.entity.smali.Register.RegisterInfo;
import com.litecoding.smali2java.entity.smali.RegisterGroup;
import com.litecoding.smali2java.entity.smali.SmaliCodeEntity;
import com.litecoding.smali2java.entity.smali.SmaliMethod;

import dalvik.bytecode.Opcodes;

import static com.litecoding.smali2java.Consts.*;

/**
* This class converts smali entities to java entities
* @author Dmitry Vorobiev
*
*/
public class SmaliRenderer {
  public static class SmaliBlock {
    public static final AtomicInteger ID = new AtomicInteger();
   
    /**
     * Id of this block
     */
    public final int id;
   
    /**
     * List of blocks that redirect execution flow to this block.
     */
    public final List<SmaliBlock> referencedBy = new LinkedList<SmaliBlock>();
   
    /**
     * Timeline for method registers used in the current block
     */
    public final RegisterTimeline registerTimeline = new RegisterTimeline();
       
    public boolean isRootBlock = false;
   
    /**
     * Label associated with this block.
     */
    public Label smaliLabel = null;
   
    public final List<Instruction> instructions = new LinkedList<Instruction>();
   
    /**
     * Last instruction is if-*.
     */
    public boolean isEndsByCondition = false;
   
    public Instruction condition = null;
   
    /**
     * Last instruction is goto*.
     */
    public boolean isEndsByGoto = false;
   
    /**
     * Last instruction is return*
     */
    public boolean isEndsByReturn = false;
   
    public Instruction returnInstruction = null;
   
    /**
     * Link to next block in cases of true condition, goto or simple redirect.
     */
    public SmaliBlock nextBlockIfTrue = null;
   
    /**
     * Link to next block in case of false condition.
     */
    public SmaliBlock nextBlockIfFalse = null;
   
   
    /*
     * Internal variables
     */
   
    /**
     * Shows is block never used before
     */
    public boolean internalIsEmpty = true;
    public Label internalNextLabelIfTrue = null;
   
    public SmaliBlock() {
      this.id = ID.incrementAndGet();
    }
   
    public boolean isPlain() {
      return isRootBlock && isEndsByReturn;
    }
   
    @Override
    public String toString() {
      StringBuilder builder = new StringBuilder();
      final String separator = "========================================\n";
     
      builder.append(separator);
      builder.append("id: ");
      builder.append(id);
      builder.append("\n");
     
      if(isRootBlock) {
        builder.append("This is the first block\n");
      } else {
        builder.append("referenced by: ");
        for(SmaliBlock block : referencedBy) {
          builder.append(block.id);
          builder.append(" ");
        }
        builder.append("\n");
      }
           
      if(smaliLabel != null) {
        builder.append("smali label: ");
        builder.append(smaliLabel.getName());
        builder.append("\n");
      }
     
      builder.append("instructions:\n");
      for(Instruction instruction : instructions) {
        builder.append("    ");
        builder.append(instruction.toString());
        builder.append("\n");
      }
     
      builder.append("register timeline: \n");
      for(List<RegisterInfo> info : registerTimeline.getTimeline()) {
        builder.append("    ");
        builder.append(info.toString());
        builder.append("\n");
      }
     
      if(isEndsByCondition) {
        builder.append("Ends by condition: ");
        builder.append(condition.toString());
        builder.append("\n");
      } else if(isEndsByGoto) {
        builder.append("Ends by goto\n");
      } else if(isEndsByReturn) {
        builder.append("Ends by return: ");
        builder.append(returnInstruction.toString());
        builder.append("\n");
      } else {
        builder.append("Ends by beginning of the next block\n");
      }
     
      if(nextBlockIfTrue != null) {
        builder.append("next block if true: ");
        builder.append(nextBlockIfTrue.id);
        builder.append("\n");
      }
     
      if(nextBlockIfFalse != null) {
        builder.append("next block if false: ");
        builder.append(nextBlockIfFalse.id);
        builder.append("\n");
      }
     
      return builder.toString();
    }
  }
 
  /**
   * Produces list with blocks of code for the selected method.
   * Root block should be placed first.
   * @param smaliMethod
   * @return list with blocks of code
   * @throws UnknownLabelException
   */
  public static SmaliBlock generateBlocks(SmaliMethod smaliMethod) throws UnknownLabelException {
    Map<String, SmaliBlock> labeledBlocks = new HashMap<String, SmaliBlock>();
    List<SmaliBlock> allBlocks = new LinkedList<SmaliBlock>();
   
    //generate root block
    SmaliBlock rootBlock = new SmaliBlock();
    rootBlock.isRootBlock = true;
   
    SmaliBlock block = rootBlock;
   
    for(SmaliCodeEntity codeEntity : smaliMethod.getCommands()) {
      //label for outer breaking
      mainLoop:
   
      if(codeEntity instanceof Instruction) {
        Instruction instruction = (Instruction) codeEntity;
        switch(instruction.getOpcodeData().getType()) {
        case OpcodeData.TYPE_GOTO: {
          block.internalIsEmpty = false;
          block.isEndsByGoto = true;
          block.internalNextLabelIfTrue = (Label) instruction.getArguments().get(0);
         
          //register block in labeled & all
          if(block.smaliLabel != null)
            labeledBlocks.put(block.smaliLabel.getName(), block);
          allBlocks.add(block);
         
          block = new SmaliBlock();
          break mainLoop;
        }
        case OpcodeData.TYPE_CONDITION: {
          block.internalIsEmpty = false;
          block.isEndsByCondition = true;
          block.condition = instruction;
          block.internalNextLabelIfTrue =
              (Label) instruction.getArguments().get(instruction.getArguments().size() - 1);
                   
          //register block in labeled & all
          if(block.smaliLabel != null)
            labeledBlocks.put(block.smaliLabel.getName(), block);
          allBlocks.add(block);
         
          //now we should create a block that follows the current if condition is false
          SmaliBlock prevBlock = block;
          block = new SmaliBlock();
          prevBlock.nextBlockIfFalse = block;
         
          break mainLoop;
        }
        case OpcodeData.TYPE_RETURN: {
          block.internalIsEmpty = false;
          block.isEndsByReturn = true;
          block.returnInstruction = instruction;
         
          //register block in labeled & all
          if(block.smaliLabel != null)
            labeledBlocks.put(block.smaliLabel.getName(), block);
          allBlocks.add(block);
         
          block = new SmaliBlock();
          break mainLoop;
        }
        default: {
          block.internalIsEmpty = false;
          block.instructions.add(instruction);
         
          break mainLoop;
        }
        }
      } else if(codeEntity instanceof Label) {
        Label label = (Label) codeEntity;
        if(!block.internalIsEmpty) {
          //This means that end of current block is regular instruction
          //(not a condition or goto)
         
          //finishing previous block & creating the next
          block.internalNextLabelIfTrue = label;
         
          //register block in labeled & all
          if(block.smaliLabel != null)
            labeledBlocks.put(block.smaliLabel.getName(), block);
          allBlocks.add(block);
         
          block = new SmaliBlock();
        }
       
        //here we have a brand-new block
        block.internalIsEmpty = false;
        block.smaliLabel = label;
        break mainLoop;
      }
    }
   
    //resolve labels
    for(SmaliBlock currBlock : allBlocks) {
      if(currBlock.internalNextLabelIfTrue != null) {
        String labelName = currBlock.internalNextLabelIfTrue.getName();
        if(!labeledBlocks.containsKey(labelName)) {
          throw new UnknownLabelException("Unknown label: " + labelName);
        }
       
        currBlock.nextBlockIfTrue = labeledBlocks.get(labelName);
      }
     
      if(currBlock.nextBlockIfTrue != null)
        currBlock.nextBlockIfTrue.referencedBy.add(currBlock);
     
      if(currBlock.nextBlockIfFalse != null)
        currBlock.nextBlockIfFalse.referencedBy.add(currBlock);
    }
   
    //build timeline
    buildTimeline(allBlocks, smaliMethod);
   
    allBlocks.clear();
    labeledBlocks.clear();
    return rootBlock;
  }
 
  /**
   * Method for debugging. Prints to System.out block chain content and timeline
   * @param rootBlock
   */
  public static void printBlockChain(SmaliBlock rootBlock) {
    List<SmaliBlock> printed = new LinkedList<SmaliBlock>();
    List<SmaliBlock> scheduled = new LinkedList<SmaliBlock>();
    scheduled.add(rootBlock);
   
    while(!scheduled.isEmpty()) {
      SmaliBlock currBlock = scheduled.remove(0);
      if(!printed.contains(currBlock)) {
        System.out.println(currBlock);
        printed.add(currBlock);
      }
     
      if(currBlock.nextBlockIfTrue != null && !printed.contains(currBlock.nextBlockIfTrue))
        scheduled.add(currBlock.nextBlockIfTrue);
     
      if(currBlock.nextBlockIfFalse != null && !printed.contains(currBlock.nextBlockIfFalse))
        scheduled.add(currBlock.nextBlockIfFalse);
    }
  }
   
  /**
   * Builds scheme of register usage (timeline) for each block of code
   * @param block
   * @param method
   */
  private static void buildTimeline(List<SmaliBlock> blockList, SmaliMethod method) {
    buildTimelineForward(blockList, method);
    buildTimelineBackward(blockList, method);
  }
 
  /**
   * Initializes scheme of register usage (timeline) and maps method parameters, if needed.
   * @param block
   * @param method
   * @param lines
   */
  private static void initTimeline(SmaliBlock block,
      SmaliMethod method,
      ArrayList<Instruction> lines) {
    RegisterTimeline timeline = block.registerTimeline;
    lines.addAll(block.instructions);
   
    //add last line that can change registers
    if(block.isEndsByCondition) {
      lines.add(block.condition);
    } else if(block.isEndsByReturn) {
      lines.add(block.returnInstruction);
    } else {
      lines.trimToSize();
    }
   
    int linesCount = lines.size();
   
    //initialize timeline if needed
    if(!timeline.isInitialized()) {
      timeline.init(method, linesCount, block.isRootBlock);
    }
  }
 
  /**
   * Build timeline by forward scanning
   * @param blockList
   * @param method
   */
  private static void buildTimelineForward(List<SmaliBlock> blockList, SmaliMethod method) {
    SmaliBlock block = null;
    RegisterTimeline timeline = null;
   
    ArrayList<Instruction> lines = new ArrayList<Instruction>(128);
   
    for(int i = 0; i < blockList.size(); i++) {
      block = blockList.get(i);
      initTimeline(block, method, lines);
     
      timeline = block.registerTimeline;
     
      if(block.referencedBy.size() == 1) {
        //copy data from the end of previous block
        SmaliBlock refBlock = block.referencedBy.get(0);
        RegisterTimeline refTimeline = refBlock.registerTimeline;
        int lastLineIdx = refTimeline.getLinesCount() - 1;
        List<RegisterInfo> prevSlice = refTimeline.getSlice(lastLineIdx);
        RegisterTimeline.copySliceTypeData(prevSlice, timeline.getSlice(0));
      }
     
      buildBlockTimelineForward(lines, timeline);
     
      lines.clear();
    }
   
  }
 
  /**
   * Build timeline by backward scanning
   * @param blockList
   * @param method
   */
  private static void buildTimelineBackward(List<SmaliBlock> blockList, SmaliMethod method) {
    //TODO: implement backward timeline scanning
    SmaliBlock block = null;
    RegisterTimeline timeline = null;
   
    List<SmaliBlock> sortedList = new LinkedList<SmaliBlock>();
    List<SmaliBlock> tmpList = new LinkedList<SmaliBlock>();
    tmpList.addAll(blockList);
   
    while(tmpList.size() > 0) {
      block = tmpList.remove(0);
      if(block.isEndsByReturn) {
        sortedList.add(block);
      } else if(block.isEndsByCondition &&
          sortedList.contains(block.nextBlockIfTrue) &&
          sortedList.contains(block.nextBlockIfFalse)) {
        sortedList.add(block);
      } else if(block.nextBlockIfTrue != null && sortedList.contains(block.nextBlockIfTrue)) {
        sortedList.add(block);
      } else {
        tmpList.add(block);
      }
    }
   
    ArrayList<Instruction> lines = new ArrayList<Instruction>(128);
   
    for(int i = 0; i < sortedList.size(); i++) {
      block = sortedList.get(i);
      initTimeline(block, method, lines);
     
      timeline = block.registerTimeline;
     
      buildBlockTimelineBackward(lines, timeline);
     
      lines.clear();
    }
  }
 
  /**
   * Builds scheme of register usage (timeline) for the current block by parameter mapping and
   * const definition recognizing. This method scans the block forward from its head.
   * @param lines
   */
  private static void buildBlockTimelineForward(ArrayList<Instruction> lines,
      RegisterTimeline timeline) {
    int linesCount = lines.size();
   
    //processing all instructions
    Instruction instruction = null;
    List<RegisterInfo> currSlice = null;
    List<RegisterInfo> prevSlice = null;
    for(int i = 0; i < linesCount; i++) {
      instruction = lines.get(i);
      prevSlice = currSlice;
      currSlice = timeline.getSlice(i);
     
      //filling type of destination register
      String dstType = "(UNKNOWN)";
      OpcodeData opcodeData = instruction.getOpcodeData();
      if(opcodeData.getType() == OpcodeData.TYPE_GET) {
        //TODO: handle aget*
        List<SmaliCodeEntity> args = instruction.getArguments();
        FieldRef srcField = (FieldRef) args.get(args.size() - 1);
        dstType = srcField.getType();
      } else if(opcodeData.getType() == OpcodeData.TYPE_CONST) {
        if(opcodeData.getOpcode() == Opcodes.OP_CONST_STRING)
          dstType = "Ljava/lang/String;";
        else
          dstType = "(BY CONST)";
      } else if(opcodeData.getType() == OpcodeData.TYPE_NEW) {
        if(opcodeData.getOpcode() == Opcodes.OP_NEW_INSTANCE) {
          List<SmaliCodeEntity> args = instruction.getArguments();
          ClassRef srcClass = (ClassRef) args.get(args.size() - 1);
          dstType = srcClass.getName();
        } else
          dstType = "(BY NEW)";
      }

     
      for(SmaliCodeEntity entity : instruction.getArguments()) {
        if(entity instanceof Register) {
          Register var = (Register) entity;
          boolean is64bit = var.info.is64bit;
          if(var.isParameter()) {
            int idx = var.getMappedId();
            RegisterTimeline.setRegisterRWFlags(currSlice,
                idx,
                BOOL_TRUE,
                BOOL_KEEP,
                is64bit);
          } else {
            int idx = var.getId();
            if(var.isDestination()) {
              //There are one or none destination registers.
              //So, fill the type by dstType value
              RegisterTimeline.setRegisterRWFlags(currSlice,
                  idx,
                  BOOL_KEEP,
                  BOOL_TRUE,
                  is64bit);
              currSlice.get(idx).type = dstType;
              if(is64bit) {
                currSlice.get(idx + 1).type = dstType;
              }
            } else {
              RegisterTimeline.setRegisterRWFlags(currSlice,
                  idx,
                  BOOL_TRUE,
                  BOOL_KEEP,
                  is64bit);
            }
          }
        } else if(entity instanceof RegisterGroup) {
          //TODO: Check 64bit registers here
          for(SmaliCodeEntity subEntity : entity.getArguments()) {
            Register var = (Register) subEntity;
            int idx = -1;
            if(var.isParameter())
              idx = var.getMappedId();
            else
              idx = var.getId();
             
            RegisterTimeline.setRegisterRWFlags(currSlice,
                idx,
                BOOL_TRUE,
                BOOL_KEEP,
                false);
          }
        }
      }
     
      if(prevSlice == null)
        continue;
     
      //copy type of register in previous slice if it wasn't modified
      for(int j = 0; j < currSlice.size(); j++) {
        RegisterInfo registerInfo = currSlice.get(j);
        if(!registerInfo.isWritten && !registerInfo.isFinallyDefined) {
          registerInfo.copyTypeDataFrom(prevSlice.get(j));
        }
      }
    }
  }
 
  private static void buildBlockTimelineBackward(ArrayList<Instruction> lines,
      RegisterTimeline timeline) {
    //TODO: implement this
  }
}
TOP

Related Classes of com.litecoding.smali2java.renderer.SmaliRenderer$SmaliBlock

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.