1. java是如何变成机器码的?

  1. 在dex2oat阶段,java生成的dex字节码经过trycompile阶段,调用code_generator_riscv64.cc,通过字节码对应的函数调用相应的汇编,汇编代码通过assembler来实现了机器码的生成。
  2. 在dex2oat阶段并不是所有的dex字节码都可以编译成机器码,有些字节码只可以做到优化,只能放在解释执行中取执行。现在的操作系统为了优化开机时间和打开时间,也不会过分地将字节码变成机器码,而是留到解释执行中进行runtime处理。
  3. 解释执行分为汇编解释执行和C++解释执行,在汇编解释执行中,字节码的实现是通过汇编指令完成的,但是此处的汇编并不是调用assembler实现的,而是工具链完成的。

2. Add指令是如何生成机器码的?

不同的汇编指令对应不同的Emit系列用来生成机器码。用最简单的系列Add系列来表示一下:

汇编语言转机器码的python脚本 汇编怎么变成机器码_操作系统

EmitR表示的是将参数通过位移和合并的方式组成一段机器码,组合的方式在riscv的官方文档里有介绍。

汇编语言转机器码的python脚本 汇编怎么变成机器码_汇编指令_02


Assembler:

void Riscv64Assembler::EmitR(int32_t funct7, Register rs2, Register rs1, int32_t funct3, Register rd, int32_t opcode) {
  uint32_t encoding = static_cast<uint32_t>(funct7) << kFunct7Shift |
                      static_cast<uint32_t>(rs2) << kRs2Shift |
                      static_cast<uint32_t>(rs1) << kRs1Shift |
                      static_cast<uint32_t>(funct3) << kFunct3Shift |
                      static_cast<uint32_t>(rd) << kRdShift |
                      opcode;
  Emit(encoding);
}

encoding就是根据riscv的规范EmitR系列完成了机器码的生成,Emit则完成了将机器码存入buffer缓存区的工作

3. 跳转指令是如何生成机器码的?

3.1 构建测试环境

假如有如下跳转汇编指令:

beq  a0,a1, 1f        // beq1
add zero, zero, zero
beq  a2,a3, 2f         // beq2       
add zero, zero, zero

beq  a0,a1, 1f        // beq3
add zero, zero, zero
beq  a2,a3, 2f          // beq4
add zero, zero, zero

1: 
add zero, zero, zero

2:
add zero, zero, zero

beq  a0,a1, 1b          // beq5
beq  a0,a1, 2b        // beq6

在assembler_riscv64_test.cc的测试中为如下代码:

void BranchCondTwoRegsHelper(void (riscv64::Riscv64Assembler::*f)(riscv64::GpuRegister,
                                                                  riscv64::GpuRegister,
                                                                  riscv64::Riscv64Label*),
                            const std::string& instr_name) {
  riscv64::Riscv64Label label1;// 初始化跳转类 label1
  riscv64::Riscv64Label label2;  // 初始化跳转类label2
  (Base::GetAssembler()->*f)(riscv64::A0, riscv64::A1, &label1);   // 如果a0== a1,跳转到label1
  __ Add(riscv64::ZERO, riscv64::ZERO, riscv64::ZERO);
  (Base::GetAssembler()->*f)(riscv64::A2, riscv64::A3, &label2);// 如果a0== a1,跳转到label2
  __ Add(riscv64::ZERO, riscv64::ZERO, riscv64::ZERO);

  (Base::GetAssembler()->*f)(riscv64::A0, riscv64::A1, &label1); 
  __ Add(riscv64::ZERO, riscv64::ZERO, riscv64::ZERO);
  (Base::GetAssembler()->*f)(riscv64::A2, riscv64::A3, &label2);
  __ Add(riscv64::ZERO, riscv64::ZERO, riscv64::ZERO);

  __ Bind(&label1);
  __ Add(riscv64::ZERO, riscv64::ZERO, riscv64::ZERO);
  __ Bind(&label2);

  (Base::GetAssembler()->*f)(riscv64::A0, riscv64::A1, &label1); 

  (Base::GetAssembler()->*f)(riscv64::A2, riscv64::A3, &label2);

  std::string expected =
      instr_name + " a0, a1, 1f\n" +
      "add zero, zero, zero\n" +
      instr_name + " a2, a3, 2f\n" +
      "add zero, zero, zero\n"
      instr_name + " a0, a1, 1f\n" +
      "add zero, zero, zero\n" +
      instr_name + " a2, a3, 2f\n" +
      "add zero, zero, zero\n"+
      "1:\n" +
      "add zero, zero, zero\n"+
      "2:\n" +
      "add zero, zero, zero\n"+
      instr_name + " a0, a1, 1b\n" +
      instr_name + " a2, a3, 2b\n" +
  DriverStr(expected, instr_name);
}

3.2 测试原理

此处编写的assembler其实就是替代了gcc的功能,assembler本身是使用c++完成的,所以不需要使用操作系统的环境。

测试的原理大概可以描述为到(Base::GetAssembler()->*f)(riscv64::A2, riscv64::A3, &label2);这段代码为调用Assembler生成机器码存入buffer中,std::string expected则是调用gcc工具链生成可执行文件后再反汇编,将两者的机器码进行对比,如果相等则测试成功。

汇编语言转机器码的python脚本 汇编怎么变成机器码_机器码_03

接着对案例进行分析,通过assembler源码了解跳转汇编是如何生成机器码的。