代码之家  ›  专栏  ›  技术社区  ›  ineedahero

英特尔X86汇编:如何判断多个位宽是一个参数?

  •  0
  • ineedahero  · 技术社区  · 6 年前

    在以下组件中:

    mov     dx, word ptr [ebp+arg_0]
    mov     [ebp+var_8], dx
    

    将其视为一个组合的C函数,(C函数的参数)arg\u 0宽多少位?(局部C变量)var\u 8宽多少位?也就是说,它是一个短的,一个int,等等。

    由此看来,var\u 8是16位,因为dx是一个16位寄存器。但我不确定arg\u 0。

    如果部件也包含此行:

    ecx, [ebp+arg_0]
    

    这是否意味着arg\u 0是一个32位的值?

    1 回复  |  直到 6 年前
        1
  •  4
  •   Peter Cordes    6 年前

    要解决这个问题,有三个原则需要理解。

    1. 汇编程序必须能够推断出正确的长度。
      虽然Intel的语法没有使用像 AT&T syntax 汇编程序仍然需要找到操作数大小的方法。

      模棱两可的指令 mov [var], 1 写为 movl $1, var 在AT&中;T语法,如果存储的大小为32位(请注意后缀 l ),因此很容易判断立即数操作数的大小。
      接受Intel语法的汇编程序需要一种方法来推断此大小,有四种广泛使用的选项:

      • 它是从另一个操作数推断出来的。
        例如,当涉及寄存器时就是这种情况。
        E、 g。 mov [var], dx 是一个16位存储。
      • 这是明确规定的。
        mov WORD [var], dx
        MASM语法汇编程序需要 PTR 在大小之后,因为它们的大小说明符只允许在内存操作数上使用,而不允许在即时操作数或其他任何地方使用。
        这是我喜欢的形式,因为它清晰、突出,而且不太容易出错( mov WORD [var], edx 无效)。
      • 它是根据上下文推断出来的。

         var db 0
        
         mov [var], 1   ; MASM/TASM only.   associate sizes with labels 
        

        MASM语法汇编程序可以推断 var 声明为 db 它的大小是8位,存储也是8位(默认情况下)。
        这是我不喜欢的形式,因为它使代码更难阅读(汇编的一个好处是指令语义的“局部性”),并且混合了类型之类的高级概念和存储大小之类的低级概念。这就是为什么 NASM's syntax doesn't support magical / non-local size association


      • 情况就是这样 push 、分支和所有指令,其中操作数大小取决于内存模型或代码大小。
        某些指令可以覆盖使用的实际大小,但默认值是明智的选择。(例如:。 push word 123 vs. push 123 )


      简而言之 必须 这是汇编程序告诉大小的一种方式,否则它将拒绝代码。(或者一些低质量的汇编程序,如emu8086,对于不明确的情况,有一个默认的操作数大小。)

      如果您正在查看已反汇编的代码,反汇编程序通常会采取安全措施,并始终明确说明大小。
      如果没有,则必须手动检查操作码,如果反汇编程序不显示操作码,则是时候更改它了。
      反汇编程序很容易找到操作数的大小,因为它要反汇编的二进制代码与CPU执行的二进制代码相同,并且指令操作码对操作数大小进行编码。

    2. C语言故意对C类型如何映射到位的数量不严格

      试图从反汇编中推断变量的类型并非徒劳,但也必须考虑平台,而不仅仅是架构。
      讨论了使用的主要模型 here :

      Datatype    LP64    ILP64   LLP64   ILP32   LP32
      char        8       8       8       8       8
      short       16      16      16      16      16
      _int32      32          
      int         32      64      32      32      16
      long        64      64      32      32      32
      long long                   64      [64]                    
      pointer     64      64      64      32      32
      

      x86\u 64上的Windows使用LLP64。x86-64上的其他操作系统通常使用x86-64 System V ABI,这是一种LP64型号。

    3. 程序集没有类型,程序员可以利用它

      Even compilers can exploit that

      在链接a的情况下 bar 类型的变量 long long (64位)与1进行OR运算, clang 通过仅对低位字节进行ORing操作来保留REX前缀。如果立即使用两个dword加载或一个qword加载重新加载变量,则会导致存储转发暂停,因此这可能不是一个好的选择,尤其是在32位模式下 or dword [bar], 1 大小相同,可能会作为两个32位的半部分重新加载。
      如果一个人不小心地查看反汇编代码,他们可以推断 酒吧 为8位。
      这种部分访问变量或对象的技巧很常见。

      为了正确猜测变量的大小,需要一些专业知识。
      例如,结构成员通常是填充的,因此它们之间有未使用的空间,这可能会欺骗没有经验的用户,使其认为每个成员都比实际大小大。
      烟囱有精确的对齐要求 may make widen the parameters size

      经验法则是,编译器通常倾向于保持堆栈16字节对齐,并自然对齐所有变量。 Multiple narrow variables are packed into a single dword 。通过堆栈传递函数参数时,每个参数都被填充为32或64位,但这不适用于堆栈上的局部变量布局。

    最后回答你的问题

    是的,从第一段代码中可以假定 arg_0 是16位宽。
    请注意,由于它是传递到堆栈上的函数arg,因此它实际上是32位的,但不使用高16位。

    如果a mov ecx, [ebp+arg_0] 出现在代码中的时间要晚于您对 arg\u 0 ,它肯定至少是32位的。
    它不太可能是64位的(64位类型在32位代码中很少见,我们可以打赌),所以我们可以断定它是32位的。
    显然,第一个代码片段是那些只使用变量的一部分的技巧之一。

    这就是你如何处理一个var大小的反向工程,你做一个猜测,验证它与代码的其余部分一致,如果不一致,重新访问它,重复。
    随着时间的推移,你会做出几乎不需要修改的猜测。