获取汇编指令的大小

9

我需要从内存中的一个小代码段逐个读取指令,并找出我在内存中拥有的指令的大小。

以下是一个原始反汇编代码示例,用于解释我的问题:

 (gdb) disas /r 0x400281,+8
 Dump of assembler code from 0x400281 to 0x400289:
    0x0000000000400281:  48 89 c7       movq   %rax, %rdi
    0x0000000000400284:  b0 00          movb   $0, %al
    0x0000000000400286:  e8 f2 48 00 00 callq  0x10001f30a
 End of assembler dump.

我知道第一条指令的内存地址(在这个例子中为p = 0x0000000000400281),并且可以从p读取每个内存地址。问题是,我无法确定*(p + offset)的值是否是操作码,并且我知道每个操作码的大小信息不是固定的。

那么,我能否获得每个汇编指令的大小?或者我能否知道我读取的值是操作码还是信息?


4
你需要一个反汇编库。 - n. m.
2
每个反汇编器都有这种知识(以及许多其他知识),但您不希望自己编写x86反汇编器,我也不能推荐一个可以做到这一点的库(即使我能,那也是离题的)。 - user395760
3
听起来像是一个 XY 问题。你为什么要解析这些指令?你要解决的整体任务是什么? - Igor Skochinsky
1
那仍然不是一个解释。 - Igor Skochinsky
1
@IgorSkochinsky:当然可以。如果这是他的问题,他可能需要解析指令以找到他想要修补的CALLs,除非他有魔法访问关于CALL指令在哪里的知识。请参阅我的答案(它甚至会告诉他哪些指令是CALLs)。 - Ira Baxter
显示剩余3条评论
4个回答

15
@AlexisWilke的回答是正确的:这很混乱。他提供了正确的见解和参考来完成工作。
我已经用C语言完成了这项工作。以下是代码;这在生产环境中使用。
注意事项:它执行了传统x86指令集的大部分,但不是全部,特别是与向量寄存器集相关的指令没有。它还包含一些我们在代码中使用的虚拟指令的解码。我认为将其扩展到x86-64不难,但会变得更加混乱。最后,这是直接提取的,但我不能保证它可以立即编译。
/* (C) Copyright 2012-2014 Semantic Designs, Inc.
   You may freely use this code provided you retain this copyright message
*/

typedef unsigned int natural;

natural InstructionLength(BYTE* pc)
{ // returns length of instruction at PC
   natural length=0;
   natural opcode, opcode2;
   natural modrm;
   natural sib;
   BYTE* p=pc;

   while (true)
    {  // scan across prefix bytes
       opcode=*p++;
       switch (opcode)
       {  case 0x64: case 0x65: // FS: GS: prefixes
      case 0x36: // SS: prefix
      case 0x66: case 0x67: // operand size overrides
      case 0xF0: case 0xF2: // LOCK, REPNE prefixes
          length++;
              break;
          case 0x2E: // CS: prefix, used as HNT prefix on jumps
          case 0x3E: // DS: prefix, used as HT prefix on jumps
              length++;
              // goto process relative jmp // tighter check possible here
              break;
           default: 
              goto process_instruction_body;
       } 
    }

process_instruction_body:
switch(opcode) // switch on main opcode
{
       // ONE BYTE OPCODE, move to next opcode without remark
       case 0x27: case 0x2F:
       case 0x37: case 0x3F:
       case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47:
       case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F:
       case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57:
   case 0x58: case 0x59: case 0x5A: case 0x5B: case 0x5C: case 0x5D: case 0x5E: case 0x5F:
       case 0x90: // nop
       case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: // xchg
   case 0x98: case 0x99:
       case 0x9C: case 0x9D: case 0x9E: case 0x9F:
       case 0xA4: case 0xA5: case 0xA6: case 0xA7: case 0xAA: case 0xAB: // string operators
       case 0xAC: case 0xAD: case 0xAE: case 0xAF:
   /* case 0xC3: // RET handled elsewhere */ 
       case 0xC9:
       case 0xCC: // int3
       case 0xF5: case 0xF8: case 0xF9: case 0xFC: case 0xFD: 
          return length+1; // include opcode

       case 0xC3: // RET
           if (*p++ != 0xCC)
              return length+1;
           if (*p++ != 0xCC)
              return length+2;
           if (*p++ == 0xCC
               && *p++ == 0xCC)
            return length+5;
        goto error;

    // TWO BYTE INSTRUCTION
    case 0x04: case 0x0C: case 0x14: case 0x1C: case 0x24: case 0x2C: case 0x34: case 0x3C:
    case 0x6A:
    case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: case 0xB7:
        case 0xC2:
           return length+2;

    // TWO BYTE RELATIVE BRANCH
       case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77:
       case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7E: case 0x7F:
       case 0xE0: case 0xE1: case 0xE2: case 0xE3: case 0xEB:
           return length+2;

       // THREE BYTE INSTRUCTION (NONE!)

   // FIVE BYTE INSTRUCTION:
       case 0x05: case 0x0D: case 0x15: case 0x1D: 
       case 0x25: case 0x2D: case 0x35: case 0x3D:
       case 0x68:
       case 0xA9:
       case 0xB8: case 0xB9: case 0xBA: case 0xBB: case 0xBC: case 0xBD: case 0xBE: case 0xBF:
        return length+5;

   // FIVE BYTE RELATIVE CALL
   case 0xE8:
         return length+5;

   // FIVE BYTE RELATIVE BRANCH
   case 0xE9:
         if (p[4]==0xCC)
                return length+6; // <jmp near ptr ...  int 3>
         return length+5; // plain <jmp near ptr>

       // FIVE BYTE DIRECT ADDRESS
       case 0xA1: case 0xA2: case 0xA3: // MOV AL,AX,EAX moffset...
         return length+5;
         break;

      // ModR/M with no immediate operand
      case 0x00: case 0x01: case 0x02: case 0x03: case 0x08: case 0x09: case 0x0A: case 0x0B:
      case 0x10: case 0x11: case 0x12: case 0x13: case 0x18: case 0x19: case 0x1A: case 0x1B:
      case 0x20: case 0x21: case 0x22: case 0x23: case 0x28: case 0x29: case 0x2A: case 0x2B:
      case 0x30: case 0x31: case 0x32: case 0x33: case 0x38: case 0x39: case 0x3A: case 0x3B:
      case 0x84: case 0x85: case 0x86: case 0x87: case 0x88: case 0x89: case 0x8A: case 0x8B: case 0x8D: case 0x8F:
      case 0xD1: case 0xD2: case 0xD3:
      case 0xFE: case 0xFF: // misinterprets JMP far and CALL far, not worth fixing
        length++; // count opcode
            goto modrm;

      // ModR/M with immediate 8 bit value
      case 0x80: case 0x82: case 0x83:
      case 0xC0: case 0xC1: 
      case 0xC6:  // with r=0?
          length+=2; // count opcode and immediate byte
            goto modrm;

      // ModR/M with immediate 32 bit value
      case 0x81: 
      case 0xC7:  // with r=0?
        length+=5; // count opcode and immediate byte
            goto modrm;

      case 0x9B: // FSTSW AX = 9B DF E0
           if (*p++==0xDF)
              { if (*p++==0xE0)
               return length+3;
            printf("InstructionLength: Unimplemented 0x9B tertiary opcode %2x at %x\n",*p,p);
                goto error;
          }
           else { printf("InstructionLength: Unimplemented 0x9B secondary opcode %2x at %x\n",*p,p);
                  goto error;
            }

      case 0xD9: // various FP instructions
           modrm=*p++;
           length++; //  account for FP prefix
           switch (modrm)
           {  case 0xC9: case 0xD0: 
          case 0xE0: case 0xE1: case 0xE4: case 0xE5: 
              case 0xE8: case 0xE9: case 0xEA: case 0xEB: case 0xEC: case 0xED: case 0xEE:
              case 0xF8: case 0xF9: case 0xFA: case 0xFB: case 0xFC: case 0xFD: case 0xFE: case 0xFF:
                  return length+1;
          default:  // r bits matter if not one of the above specific opcodes
                  switch((modrm&0x38)>>3)
                  {  case 0: goto modrm_fetched;  // fld
                 case 1: return length+1; // fxch
                 case 2: goto modrm_fetched; // fst
                 case 3: goto modrm_fetched; // fstp
                 case 4: goto modrm_fetched; // fldenv
                 case 5: goto modrm_fetched; // fldcw
                 case 6: goto modrm_fetched; // fnstenv
                 case 7: goto modrm_fetched; // fnstcw
                  }
                  goto error; // unrecognized 2nd byte
           }

      case 0xDB: // various FP instructions
           modrm=*p++;
           length++; //  account for FP prefix
           switch (modrm)
           {  case 0xE3: 
                  return length+1;
          default:  // r bits matter if not one of the above specific opcodes
#if 0
                  switch((modrm&0x38)>>3)
                  {  case 0: goto modrm_fetched;  // fld
                 case 1: return length+1; // fxch
                 case 2: goto modrm_fetched; // fst
                 case 3: goto modrm_fetched; // fstp
                 case 4: goto modrm_fetched; // fldenv
                 case 5: goto modrm_fetched; // fldcw
                 case 6: goto modrm_fetched; // fnstenv
                 case 7: goto modrm_fetched; // fnstcw
                  }
#endif
                  goto error; // unrecognized 2nd byte
           }

      case 0xDD: // various FP instructions
           modrm=*p++;
           length++; //  account for FP prefix
           switch (modrm)
           {  case 0xE1: case 0xE9: 
              return length+1;
          default:  // r bits matter if not one of the above specific opcodes
                  switch((modrm&0x38)>>3)
                  {  case 0: goto modrm_fetched;  // fld
                 // case 1: return length+1; // fisttp
                 case 2: goto modrm_fetched; // fst
                 case 3: goto modrm_fetched; // fstp
                 case 4: return length+1; // frstor
                 case 5: return length+1; // fucomp
                 case 6: goto modrm_fetched; // fnsav
                 case 7: goto modrm_fetched; // fnstsw
                  }
                  goto error; // unrecognized 2nd byte
           }

      case 0xF3: // funny prefix REPE
           opcode2=*p++;  // get second opcode byte
           switch (opcode2)
       {  case 0x90: // == PAUSE
          case 0xA4: case 0xA5: case 0xA6: case 0xA7: case 0xAA: case 0xAB: // string operators
             return length+2;
              case 0xC3: // (REP) RET
                 if (*p++ != 0xCC)
                    return length+2; // only (REP) RET
                 if (*p++ != 0xCC)
                    goto error;
                 if (*p++ == 0xCC)
                    return length+5; // (REP) RET CLONE IS LONG JUMP RELATIVE
                 goto error;
              case 0x66: // operand size override (32->16 bits)
         if (*p++ == 0xA5) // "rep movsw"
                    return length+3;
                 goto error;
              default: goto error;
           }

      case 0xF6: // funny subblock of opcodes
            modrm=*p++;
            if ((modrm & 0x20) == 0)
               length++; // 8 bit immediate operand
            goto modrm_fetched; 

      case 0xF7: // funny subblock of opcodes
            modrm=*p++;
            if ((modrm & 0x30) == 0)
               length+=4; // 32 bit immediate operand
            goto modrm_fetched; 

      // Intel's special prefix opcode
      case 0x0F:
        length+=2; // add one for special prefix, and one for following opcode
            opcode2=*p++;
        switch(opcode2) 
        { case 0x31: // RDTSC
             return length;

          // CMOVxx
          case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: 
              case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F:
              goto modrm;

              // JC relative 32 bits
              case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: 
              case 0x88: case 0x89: case 0x8A: case 0x8B: case 0x8C: case 0x8D: case 0x8E: case 0x8F:
                  return length+4; // account for subopcode and displacement

          // SETxx rm32
              case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: 
              case 0x98: case 0x99: case 0x9A: case 0x9B: case 0x9C: case 0x9D: case 0x9E: case 0x9F:
                  goto modrm;

              case 0xA2: // CPUID
                  return length+2;

              case 0xAE: // LFENCE, SFENCE, MFENCE
                  opcode2=*p++;
                  switch (opcode2)
                  { case 0xE8: // LFENCE
                case 0xF0: // MFENCE
                    case 0xF8: // SFENCE
                  return length+1;
                    default: 
                      printf("InstructionLength: Unimplemented 0x0F, 0xAE tertiary opcode in clone  %2x at %x\n",opcode2,p-1);
                  goto error;
                  }

              case 0xAF: // imul
              case 0xB0: // cmpxchg 8 bits
                  goto error;

              case 0xB1: // cmpxchg 32 bits
              case 0xB6: case 0xB7: // movzx
              case 0xBC: /* bsf */ case 0xBD: // bsr
              // case 0xBE: case 0xBF: // movsx 
              case 0xC1: // xadd
              case 0xC7: // cmpxchg8b
                  goto modrm;

              default:
                  printf("InstructionLength: Unimplemented 0x0F secondary opcode in clone %2x at %x\n",opcode,p-1);
                  goto error;
    } // switch

 // ALL THE THE REST OF THE INSTRUCTIONS; these are instructions that runtime system shouldn't ever use
     default: 
     /* case 0x26: case 0x36: // ES: SS: prefixes
        case 0x9A:
        case 0xC8: case 0xCA: case 0xCB: case 0xCD: case 0xCE: case 0xCF:
        case 0xD6: case 0xD7:
        case 0xE4: case 0xE5: case 0xE6: case 0xE7: case 0xEA: case 0xEB: case 0xEC: case 0xED: case 0xEF:
        case 0xF4: case 0xFA: case 0xFB:
         */
     printf("InstructionLength: Unexpected opcode %2x\n",opcode);
         goto error;
    }

modrm:
    modrm=*p++;
modrm_fetched:
    if (trace_clone_checking)
       printf("InstructionLength: ModR/M byte %x %2x\n",pc,modrm);
    if (modrm >= 0xC0)
       return length+1;  // account for modrm opcode
    else
    {  /* memory access */
        if ((modrm & 0x7) == 0x04)
    { /* instruction with SIB byte */
                length++; // account for SIB byte
                sib=*p++; // fetch the sib byte
                if ((sib & 0x7) == 0x05)
                   {  if ((modrm & 0xC0) == 0x40)
                     return length+1+1; // account for MOD + byte displacment
                  else return length+1+4; // account for MOD + dword displacement
                   }
            }
        switch(modrm & 0xC0)
        {  case 0x0:
          if ( (modrm & 0x07) == 0x05)
                  return length+5; // 4 byte displacement
              else return length+1; // zero length offset
           case 0x80:
              return length+5;  // 4 byte offset
          default:
      return length+2;  // one byte offset
        }
   }

error:
    {  printf("InstructionLength: unhandled opcode at %8x with opcode %2x\n",pc,opcode);
    }
    return 0; // can't actually execute this
}

1
请添加x86-64支持。 - CHRIS
如果我添加了悬赏,你会受到激励吗? - CHRIS
1
短期内还不够。我可能需要在未来两年内将这个逻辑扩展到64位,但我不知道何时必须这样做。在短期内,有很多其他事情正在吸引我的注意力。 - Ira Baxter

5
解码指令并不是很复杂。但是,由于英特尔处理器系列是CISC架构,因此这项任务变得相当困难。
首先,你不应该用汇编语言编写它,因为这可能需要一两年的时间,但也许你有时间去做。由于你只需要扫描代码,而不需要打印结果,所以你可以比实际的反汇编程序更快地完成工作。话虽如此,你会遇到同样的主要问题。
首先,手册在那里:

http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html?iid=tech_vt_tech+64-32_manuals

我建议使用这个:

http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.pdf

然后,你所要做的就是读取一个字节并理解它。在第770页上有一张表格,显示了从操作码到指令的编码方式。
例如,0x33表示使用Gb,Ev作为参数进行异或运算。G表示在接下来的ModR/M中定义的通用寄存器。然后b是大小(字节)。E表示在那一个字节之后还有一个ModR/M(对于G和E来说是同一个字节)。因此,您将需要读取那个字节以确定寻址模式,并从中确定寄存器(可以忽略)和地址大小。地址(Ev)可能是另一个寄存器(然后没有额外的字节),也可能是立即数据(1、2、4、8字节),或者是一个地址(同样是1、2、4、8字节)。很简单,对吧?请注意,所有指令都使用完全相同的ModR/M,因此只需实现一次即可。此外,在指令代码之后添加字节的顺序始终完全相同。
在地址或立即数(如果我没记错的话)之前,是64位指令的额外Mod。其中一个定义了附加模式和扩展寄存器的支持。所有这些都在我之前提到的文档中详细描述。
更或者少一些,您需要让您的解析器理解ModR/M、SIB、前缀,然后就可以了。这并不复杂。然后第一个字节告诉您指令(如果第一个字节是0x0F,则为前两个字节...)
某些指令还支持前缀来调整操作数的大小和其他类似的事情。据我所知,只有0x66(操作数大小)和0x67(地址大小)对地址和立即数据的大小有影响。其他前缀不会影响指令使用的字节数,因此您可以简单地忽略它们(嗯,计算它们,但不需要知道后面的内容)。

总之,使用LLVM库(正如评论中有人提到的)可能是更好/更容易的选择,尽管如果你的内容有限,它可能比你需要的要大得多。


1
LLVM对于OP的请求来说太过笨重了。你可以用几百行C代码来完成这个任务。请看我的回答。 - Ira Baxter
是的,我也写了自己的反汇编器。对于6502(很久以前)和Intel,例如Pentium 4之类的处理器。问题在于你有大量的指令(如果你想要处理它们的话)。此外,如果你想要正确,你需要知道处理器,因为某些代码在不同版本中的工作方式不同(例如0x0F)。 - Alexis Wilke
1
@IraBaxter:使用其他人维护的库有一个巨大的优势,那就是你不必为新扩展(如AVX和AVX512)不断更新它,这些扩展将现有的非法序列重新定义为新的前缀。或者新的SSE*扩展,其中新的强制前缀会产生新的指令。除了LLVM之外,GNU操作码库还被GDB和objdump使用,参见https://www.gnu.org/software/binutils/。 - Peter Cordes
1
@PeterCordes:同意,只要它以直接的方式完成你所需的工作,并且他们保持更新。很可能LLVM和GNU保持最新状态。它们仍然是非常好用的工具。 (在我的情况下,我不需要解码每条指令,只需要解码编译器生成的内容)。 - Ira Baxter

3

Intel有一款名为XED的库,可以处理x86/x86_64指令:https://github.com/intelxed/xed,同时也是在x86和x86_64模式下处理英特尔机器码的唯一正确方式。Intel使用它(并且它曾是Pin的一部分):https://software.intel.com/en-us/articles/xed-x86-encoder-decoder-software-library

https://software.intel.com/sites/landingpage/pintool/docs/67254/Xed/html/main.html XED用户指南(2014年) https://software.intel.com/sites/landingpage/pintool/docs/56759/Xed/html/main.html XED2用户指南(2011年)

xed_decode函数将为您提供有关指令的所有信息:https://intelxed.github.io/ref-manual/group__DEC.html https://intelxed.github.io/ref-manual/group__DEC.html#ga9a27c2bb97caf98a6024567b261d0652

xed_ild_decode 只会解码其长度的指令: https://intelxed.github.io/ref-manual/group__DEC.html#ga4bef6152f61997a47c4e0fe4327a3254

XED_DLL_EXPORT xed_error_enum_t xed_ild_decode    (   xed_decoded_inst_t *    xedd,
const xed_uint8_t *   itext,
const unsigned int    bytes 
)     

This function just does instruction length decoding. It does not return a fully decoded instruction.

Parameters

  • xedd the decoded instruction of type xed_decoded_inst_t . Mode/state sent in via xedd; See the xed_state_t .
  • itext the pointer to the array of instruction text bytes
  • bytes the length of the itext input array. 1 to 15 bytes, anything more is ignored.

Returns:

xed_error_enum_t indiciating success (XED_ERROR_NONE) or failure. Only two failure codes are valid for this function: XED_ERROR_BUFFER_TOO_SHORT and XED_ERROR_GENERAL_ERROR. In general this function cannot tell if the instruction is valid or not. For valid instructions, XED can figure out if enough bytes were provided to decode the instruction. If not enough were provided, XED returns XED_ERROR_BUFFER_TOO_SHORT. From this function, the XED_ERROR_GENERAL_ERROR is an indication that XED could not decode the instruction's length because the instruction was so invalid that even its length may across implementations.

要从由xed_ild_decode填充的xedd结构中获取长度,请使用xed_decoded_inst_get_length: https://intelxed.github.io/ref-manual/group__DEC.html#gad1051f7b86c94d5670f684a6ea79fcdf
static XED_INLINE xed_uint_t xed_decoded_inst_get_length  (   const xed_decoded_inst_t *  p   )   

Return the length of the decoded instruction in bytes.

示例代码(由英特尔2016年发布的“Apache许可证,版本2.0”):https://github.com/intelxed/xed/blob/master/examples/xed-ex-ild.c

#include "xed/xed-interface.h"
#include <stdio.h>

int main()
{
    xed_bool_t long_mode = 1;
    xed_decoded_inst_t xedd;
    xed_state_t dstate;
    unsigned char itext[15] = { 0xf2, 0x2e, 0x4f, 0x0F, 0x85, 0x99,
                                0x00, 0x00, 0x00 };

    xed_tables_init(); // one time per process

    if (long_mode) 
        dstate.mmode=XED_MACHINE_MODE_LONG_64;
    else 
        dstate.mmode=XED_MACHINE_MODE_LEGACY_32;

    xed_decoded_inst_zero_set_mode(&xedd, &dstate);
    xed_ild_decode(&xedd, itext, XED_MAX_INSTRUCTION_BYTES);
    printf("length = %u\n",xed_decoded_inst_get_length(&xedd));

    return 0;
}

任何其他解决方案,例如手动前缀/操作码解析或使用第三方反汇编器,可能会在某些罕见情况下给您错误的结果。我们不知道英特尔内部使用哪个库来验证其硬件指令解码器,但 xed 是他们各种二进制工具中软件解码器使用的库。xed 的 ild 解码器有超过 1600 行代码:https://github.com/intelxed/xed/blob/master/src/dec/xed-ild.c,应该比任何其他库更精确。

1
可能会对一些较新的专有第三方x86/x86_64扩展(例如VIA和AMD)产生错误结果:https://github.com/intelxed/xed/issues/44。但是xed的指令数据库非常好:https://github.com/intelxed/xed/tree/master/datafiles(并且文档不是很好:https://github.com/intelxed/xed/issues/39、https://github.com/intelxed/xed/pull/42和https://groups.google.com/forum/#!topic/golang-dev/GFv83tlhb4A;但是有misc/engineering-notes.txt的https://github.com/intelxed/xed/pull/42/commits/14950b1f8421a3b2e1581e1bb535efcd1c0bc354)。 - osgx

2

有一个小型的反汇编库叫做udis86:http://udis86.sourceforge.net/

该库很小并且有良好的文档。如果您通过ud_set_syntax设置翻译器为NULL,那么函数ud_disassemble只会解码指令并返回字节数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接