这套指令有什么作用?
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
这个:
获取 rip
中的地址,并向其添加 0x33b780
。此时rip
包含下一条指令的地址,即0x7ffff7a97760
。添加 0x33b780
得到 0x7ffff7dd2ee0
,这是评论中的地址。
它将存储在该地址的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$
顺便说一句,基于指令指针计算偏移量的原因是为了提高位置无关代码的效率,这对于共享库是必需的。硬编码内存地址和共享库不能很好地混合使用,但如果您知道代码和数据始终至少保持相同的距离,那么通过指令指针引用代码和数据将为您提供一种生成可重定位代码的简单方法。没有这种能力,通常需要有一个间接层,因为相对分支通常在范围内受到限制。
这套指令有什么作用?
7ffff7a97759 mov 0x33b780(%rip),%rax # 0x7ffff7dd2ee0
7ffff7a97760 mov (%rax),%rax
7ffff7a97763 test %rax,%rax
7ffff7a97766 jne 0x7ffff7a9787a
我不明白这些指令的作用,有人可以解释一下吗?
一步一个脚印...
7ffff7a97759 mov 0x33b780(%rip),%rax # 0x7ffff7dd2ee0
这个:
获取
rip
中的地址,并向其添加0x33b780
。此时rip
包含下一条指令的地址,即0x7ffff7a97760
。添加0x33b780
得到0x7ffff7dd2ee0
,这是评论中的地址。它将存储在该地址的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$
顺便说一句,基于指令指针计算偏移量的原因是为了提高位置无关代码的效率,这对于共享库是必需的。硬编码内存地址和共享库不能很好地混合使用,但如果您知道代码和数据始终至少保持相同的距离,那么通过指令指针引用代码和数据将为您提供一种生成可重定位代码的简单方法。没有这种能力,通常需要有一个间接层,因为相对分支通常在范围内受到限制。