ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

C++虚函数调用简单分析

2022-02-23 03:31:07  阅读:191  来源: 互联网

标签:sub parent cfi C++ rbp 函数调用 简单 rax movq


C++代码如下:

class parent_parent
{
public:
    virtual int print() const
    {
        return 1;
    }
};

class sub : public parent_parent
{
public:
    int print() const override
    {
        return 0;
    }
};

int main()
{
    parent_parent* p = new sub;
    p->print();
    delete p;
    return 0;
}

通过使用反汇编可得到main函数如下:

main:
.LFB2:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	pushq	%rbx
	subq	$24, %rsp
	.cfi_offset 3, -24
	movl	$8, %edi
	call	_Znwm@PLT # 此处调用operator new(unsigned long)@PLT分配内存
	movq	%rax, %rbx # 保存new的返回值到rbx寄存器
	movq	%rbx, %rdi # 复制返回值到rdi寄存器
	call	_ZN3subC1Ev # 此处调用sub::sub()构造函数
	movq	%rbx, -24(%rbp)
	movq	-24(%rbp), %rax
	movq	(%rax), %rax
	movq	(%rax), %rdx # 将print函数指针移动至rdx
	movq	-24(%rbp), %rax
	movq	%rax, %rdi
	call	*%rdx # 此处调用p->print()函数
	movq	-24(%rbp), %rax
	testq	%rax, %rax
	je	.L8
	movl	$8, %esi
	movq	%rax, %rdi
	call	_ZdlPvm@PLT
.L8:
	movl	$0, %eax
	addq	$24, %rsp
	popq	%rbx
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc

parent_parent构造函数如下:

_ZN13parent_parentC2Ev:
.LFB5:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movq	%rdi, -8(%rbp)
	leaq	16+_ZTV13parent_parent(%rip), %rdx # 加载虚表地址 + rip地址 + 16,即parent_parent::print()函数的地址,放入rdx寄存器
	movq	-8(%rbp), %rax
	movq	%rdx, (%rax) # 放入rax内地址指向的内存,即new分配的内存
	nop
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc

以及sub构造函数如下:

_ZN3subC2Ev:
.LFB7:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$16, %rsp
	movq	%rdi, -8(%rbp) 
	movq	-8(%rbp), %rax
	movq	%rax, %rdi # 保存new地址
	call	_ZN13parent_parentC2Ev #调用parent_parent::parent_parent()
	leaq	16+_ZTV3sub(%rip), %rdx # 加载虚表地址 + rip地址 + 16,sub::print()函数的地址,放入rdx寄存器
	movq	-8(%rbp), %rax
	movq	%rdx, (%rax) # 放入rax内地址指向的内存,即new分配的内存
	nop
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc

调用过程大致如下:
先调用new函数分配内存,然后调用父类构造器,获取父类虚函数地址。然后,查询子类虚表,覆盖父类函数。调用子类override的函数

虚表构成:
vtable of parent_parent:

vtable for parent_parent:
	.quad	0
	.quad	typeinfo for parent_parent # 类型信息地址
	.quad	parent_parent::print() const # print函数地址
	.weak	typeinfo for sub
	.section	.data.rel.ro._ZTI3sub,"awG",@progbits,typeinfo for sub,comdat
	.align 8
	.type	typeinfo for sub, @object
	.size	typeinfo for sub, 24

vtable for sub:

vtable for sub:
	.quad	0
	.quad	typeinfo for sub
	.quad	sub::print() const
	.weak	vtable for parent_parent
	.section	.data.rel.ro.local._ZTV13parent_parent,"awG",@progbits,vtable for parent_parent,comdat
	.align 8
	.type	vtable for parent_parent, @object
	.size	vtable for parent_parent, 24

所以根据以上原理,可以通过如下方式调用虚表中的函数:

#include <iostream>

using namespace std;

class subclass
{
public:
    virtual void print()
    {
        cout << "hello world !\n";
    }
};

int main()
{
    subclass* sub = new subclass;
    // sub 指向的地址存放 vtable存放函数的地址
    void* vtable16 = *(void**)sub;
	// 通过存放函数地址的地址取得函数地址
    void* funcs = *(void**)vtable16;
    // 获取print函数的地址
    auto print_ptr = (void(*)())(funcs);
	// 通过函数地址调用函数
    print_ptr();
    delete sub;
    return 0;
}

标签:sub,parent,cfi,C++,rbp,函数调用,简单,rax,movq
来源: https://www.cnblogs.com/ug-nan/p/15925696.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有