BruceChen7 / gitblog

My blog
6 stars 1 forks source link

汇编基础知识 #18

Open BruceChen7 opened 4 years ago

BruceChen7 commented 4 years ago

资料

Constant integer data

register

Memory

Moving Data

movq $0x4, %rax 
movq $-147, (%rax)
movq %rax, %rdx
movq %rax, (%rdx)
movq (%rax), %rdx

Arithmetic operations

Memory Addressing Modes

Address Computation Instruction

条件语句

CMP   s2, s1   s1 - s2          compare
cmpb        compare byte
cmpw        compare word  
cmpl        compare double word

TEST s2, s1    s1 & s2
testb          test byte
testw          test word
testl          test double word

set指令

Instruction Synonym Effect Set condition ----
sete D setz D ← ZF Equal / zero
setne D setnz D ← ~ZF Not equal not zero
sets D D ← SF Negative
setns D D ← ~SF Nonnegative
setg D setnle D ← ~(SF ^ OF) & ~ZF Greater (signed >)
setl D setnge D ← SF ^ OF Less (signed <)
setle D setng D ← (SF ^ OF) ZF Less or equal (signed <=)
seta D setnbe D ← ~CF & ~ZF Above (unsigned >)
setae D setnb D ← ~CF Above or equal (unsigned >=)
setb D setnae D ← CF Below (unsigned <)
setbe D setna D ← CF ZF Below or equal (unsigned <=)

jump指令

未完待续

反汇编工具

指针和引用的反汇编

#include <cstdio>

void funRef(int &ref){
    ref++;
}

int main(){
    //定义int类型变量
    int var = 0x41;
    //int指针变量,初始化为变量var的地址
    int *pnVar = &var;
    //取出指针pcVar指向的地址内容并显示
    char *pcVar = (char*)&var;
    printf("%s",pcVar);
    //引用作为参数,即把var的地址作为参数
    funRef(var);
    return 0;
}

反汇编:

image image

对于 pnVar = &var 这个语句,我们看是对应于两条指令,用一个栈空间保存了该地址。pcVar也是这样。对于引用,则是编译器计算了地址,然后将将其赋值给rdi。对于函数调用:rdi是第一个参数,rsi是第2个。所以我们在看调用printf的反汇编的时候,看到了如下的几种方式

movq -8(%rbp), %rax
movq %rax, %rsi
movl $.LC0, %edi
movl $0, %eax
call printf

引用和指针的区别:只是引用类型是通过编译器实现寻址,而指针需要手动寻址。 手动寻址的意思是要用栈空间保存该指针的地址

我们看看返回的引用:

直接就是leaq指令将地址返回给rax,所以这样做,在程序返回后是无效的地址。

this指针

#include <stdio.h>
class Location{
public:
    Location(){
        //this指针指向一块16字节的内存区域
        m_x = 1;
        //m_x是一个8字节类型,所以mov一个4字
        //mov DWORD PTR [rax], 1
        m_y = 2;
        //mov WORD PTR [rax+4], 2
    }
​
​
    short getY(){
        //获取this指针(对象首地址)偏移4处的数据,即m_y的值
        //movzx eax, WORD PTR [rbp-8+4]
        return m_y;
    }
​
​
private:
    int m_x; //占4字节
    short m_y; //占2字节
    //由于内存对齐,整个对象占8字节
};
​
int main(){
    // 在栈上分配16字节,其中有8字节分配给是loc
    // 把栈上loc的内存地址(即this指针)作为参数调用Location构造函数。
    Location loc;
​
    // 把栈上loc的内存地址(即this指针)作为参数调用getY成员函数。
    short y = loc.getY();
    //y变量位于[rbp-2]处
    //mov WORD PTR [rbp-2], ax
    return 0;
}

这里经过反汇编:

见下图

我们看看,这里首先分配了16个字节,然后,将rdi设置成该改地址,而rdi就是this指针的值。this指针中保存了所属对象的首地址。在调用成员函数的过程中,编译器利用`rdi寄存器保存了对象的首地址,并以寄存器传参的方式传递到成员函数中。

虚函数

class BaseClass {
public:
   BaseClass(){x=1;y=2;};
   virtual void vfunc1() = 0;
   virtual void vfunc2(){};
   virtual void vfunc3();
   virtual void vfunc4(){};
   void hello(){printf("hello,y=%d",this->y);};
private:
   int x;//4字节
   int y;
};
​
​
class SubClass : public BaseClass {
public:
   SubClass(){z=3;};
   virtual void vfunc1(){};
   virtual void vfunc3();
   virtual void vfunc5(){};
private:
   int z;
};
int main() {
   //①调用new操作符,在堆上动态分配一块SubClass大小的内存,rax指向这块内存的开始
   //②rax的值作为参数调用SubClass构造函数 
   BaseClass *a_ptr = new SubClass();
   //对象首地址作为参数调用函数call_vfunc
   call_vfunc(a_ptr);
​
​
   //一般的成员函数,不在虚表里
   a_ptr->hello();
} 

虚函数的布局:

SubClass 中包含两个指向属于BaseClass的函数( BaseClass::vfunc2 和 BaseClass::vfunc4)的指针。这是因为 SubClass 并没有重写这2个函数,而是直接继承自BaseClass 。

由于没有针对纯虚函数BaseClass::vfunc1的实现,因此,在 BaseClass的虚表中并没有存储 vfunc1 的地址。 这时,编译器会插入一个错误处理函数的地址,名为 purecall,万一被调用,它会令程序终止或者其他编译器想要发生的行为。

另外,一般的成员函数不在虚表里面,因为不涉及动态调用,如BaseClass中的hello()函数。