这套指令有什么作用?

What does this set of instructions do?

这套指令有什么作用?

   7ffff7a97759    mov    0x33b780(%rip),%rax        # 0x7ffff7dd2ee0
   7ffff7a97760    mov    (%rax),%rax
   7ffff7a97763    test   %rax,%rax
   7ffff7a97766    jne    0x7ffff7a9787a

我不明白这些指令的作用,有人可以解释一下吗?

一步一个脚印...

7ffff7a97759    mov    0x33b780(%rip),%rax        # 0x7ffff7dd2ee0

这个:

  1. 获取 rip 中的地址,并向其添加 0x33b780。此时rip包含下一条指令的地址,即0x7ffff7a97760。添加 0x33b780 得到 0x7ffff7dd2ee0,这是评论中的地址。

  2. 它将存储在该地址的8字节值复制到rax

让我们同意将这个 8 字节值称为 "the pointer"。根据地址的值,0x7ffff7dd2ee0 几乎可以肯定是堆栈上的一个位置。

7ffff7a97760    mov    (%rax),%rax

这会将存储在指针地址处的 8 字节值复制到 rax

7ffff7a97763    test   %rax,%rax

这对 rax 与自身执行按位与运算,丢弃结果,但修改标志。

7ffff7a97766    jne    0x7ffff7a9787a

如果按位与的结果不为零,换句话说,如果存储在 rax 中的值不为零,则跳转到位置 0x7ffff7a9787a

总之,这意味着 "find the 8 byte value stored at the address contained in the pointer indicated by rip plus 0x33b780, and if that value is not zero, jump to location 0x7fff7a9787a"。例如,在 C 术语中,存储在 0x7ffff7dd2ee0 的指针可能是一个 long *,此代码检查它指向的 long 是否包含 0.

它在 C 中的等价物可能是这样的:

long l = 0;
long * p = &l;   /*  Assume address of p is 0x7ffff7dd2ee0  */


/*  Assembly instructions in your question start here  */

if ( *p == 0 ) {
    /*  This would be the instruction after the jne  */
    /*  Do stuff  */
}

/*  Location 0x7ffff7a9787a would be here, after the if block  */
/*  Do other stuff  */

这里有一个完整的程序,展示了这个结构的使用,唯一的区别是我们找到我们的指针是参考帧指针,而不是指令指针:

.global _start

        .section .rodata

iszerostr:      .ascii  "Value of a is zero\n"
isntzerostr:    .ascii  "Value of a is not zero\n"

        .section .data

a:      .quad   0x00                    #  We'll be testing this for zero...

        .section .text

_start:
        mov     %rsp, %rbp              #  Initialize rbp
        sub     , %rsp               #  Allocate stack space
        lea     (a), %rax               #  Store pointer to a in rax...
        mov     %rax, -16(%rbp)         #  ...and then store it on stack

        #  Start of the equivalent of your code

        mov     -16(%rbp), %rax         #  Load pointer to a into rax
        mov     (%rax), %rax            #  Dereference pointer and get value
        test    %rax, %rax              #  Compare pointed-to value to zero
        jne     .notzero                #  Branch if not zero

        #  End of the equivalent of your code

.zero:
        lea     (iszerostr), %rsi       #  Address of string
        mov     , %rdx               #  Length of string
        jmp     .end

.notzero:
        lea     (isntzerostr), %rsi     #  Address of string
        mov     , %rdx               #  Length of string

.end:
        mov     , %rax                #  write() system call number
        mov     , %rdi                #  Standard output
        syscall                         #  Make system call

        mov     , %rax               #  exit() system call number
        mov     [=15=], %rdi                #  zero exit status
        syscall                         #  Make system call

输出:

paul@thoth:~/src/asm$ as -o tso.o tso.s; ld -o tso tso.o
paul@thoth:~/src/asm$ ./tso
Value of a is zero
paul@thoth:~/src/asm$ 

顺便说一句,基于指令指针计算偏移量的原因是为了提高位置无关代码的效率,这对于共享库是必需的。硬编码内存地址和共享库不能很好地混合使用,但如果您知道代码和数据始终至少保持相同的距离,那么通过指令指针引用代码和数据将为您提供一种生成可重定位代码的简单方法。没有这种能力,通常需要有一个间接层,因为相对分支通常在范围内受到限制。