c++ virtual call分析

news2024/9/19 17:27:02

Vcall Analysis

  • 1.Example分析
  • 2.识别virtual call
  • 3.CHA算法
    • 3.1.callsite到类型的map
    • 3.2.获取调用目标

c++通过virtual call实现多态性,比如下面case中通过基类指针 basePtr 进行调用时,show 会调用子类的函数,而 display 则会调用其自己的实现。

class Base {
private:
    int a, b;
    
public:
    Base(int aa, int bb): a(aa), b(bb) {}

    // 虚函数
    virtual void show() {
    }

    virtual void showcase() {

    }

    // 非虚函数
    void display() {
    }

    // 虚析构函数,保证正确的析构顺序
    virtual ~Base() {}
};

// 派生类
class Derived : public Base {
private:
    int a, b;
    
public:
    Derived(int aa, int bb): Base(aa, bb) {}

    // 重写虚函数
    void show() override {
    }

    void showcase() override {
        
    }

    // 重写非虚函数(注意,这不是多态的)
    void display() {
    }
};

class Derived1 : public Base {
private:
    int a, b;
    
public:
    Derived1(int aa, int bb): Base(aa, bb) {}

    // 重写虚函数
    void show() override {
    }

    void showcase() override {
        
    }

    // 重写非虚函数(注意,这不是多态的)
    void display() {
    }

    ~Derived1() override {

    }
};

int main(int argc, char** argv) {
    // 基类指针指向派生类对象
    Base* basePtr;
    if (argc == 1)
        basePtr = new Derived(10, 20);
    else
        basePtr = new Derived1(10, 20);
    // 虚函数调用(根据对象类型,调用 Derived 类的函数)
    basePtr->show();    // 输出:Derived class show function
    // 非虚函数调用(根据指针类型,调用 Base 类的函数)
    basePtr->display(); // 输出:Base class display function

    // 释放内存
    delete basePtr;

    return 0;
}

这篇blog主要从LLVM IR层面查看virtual call以及分析现有virtual call分析算法。

1.Example分析

将上面c代码编译为LLVM IR后,IR文本为:

类型及全局变量定义

%class.Base = type { i32 (...)**, i32, i32 }
%class.Derived = type { %class.Base, i32, i32 }
%class.Derived1 = type { %class.Base, i32, i32 }

@_ZTV7Derived = linkonce_odr unnamed_addr constant { [6 x i8*] } { [6 x i8*] [i8* null, i8* bitcast ({ i8*, i8*, i8* }* @_ZTI7Derived to i8*), i8* bitcast (void (%class.Derived*)* @_ZN7Derived4showEv to i8*), i8* bitcast (void (%class.Derived*)* @_ZN7Derived8showcaseEv to i8*), i8* bitcast (%class.Derived* (%class.Derived*)* @_ZN7DerivedD1Ev to i8*), i8* bitcast (void (%class.Derived*)* @_ZN7DerivedD0Ev to i8*)] }, align 8
@_ZTVN10__cxxabiv120__si_class_type_infoE = external global i8*
@_ZTS7Derived = linkonce_odr hidden constant [9 x i8] c"7Derived\00", align 1
@_ZTVN10__cxxabiv117__class_type_infoE = external global i8*
@_ZTS4Base = linkonce_odr hidden constant [6 x i8] c"4Base\00", align 1
@_ZTI4Base = linkonce_odr hidden constant { i8*, i8* } { i8* bitcast (i8** getelementptr inbounds (i8*, i8** @_ZTVN10__cxxabiv117__class_type_infoE, i64 2) to i8*), i8* inttoptr (i64 add (i64 ptrtoint ([6 x i8]* @_ZTS4Base to i64), i64 -9223372036854775808) to i8*) }, align 8
@_ZTI7Derived = linkonce_odr hidden constant { i8*, i8*, i8* } { i8* bitcast (i8** getelementptr inbounds (i8*, i8** @_ZTVN10__cxxabiv120__si_class_type_infoE, i64 2) to i8*), i8* inttoptr (i64 add (i64 ptrtoint ([9 x i8]* @_ZTS7Derived to i64), i64 -9223372036854775808) to i8*), i8* bitcast ({ i8*, i8* }* @_ZTI4Base to i8*) }, align 8
@_ZTV4Base = linkonce_odr unnamed_addr constant { [6 x i8*] } { [6 x i8*] [i8* null, i8* bitcast ({ i8*, i8* }* @_ZTI4Base to i8*), i8* bitcast (void (%class.Base*)* @_ZN4Base4showEv to i8*), i8* bitcast (void (%class.Base*)* @_ZN4Base8showcaseEv to i8*), i8* bitcast (%class.Base* (%class.Base*)* @_ZN4BaseD1Ev to i8*), i8* bitcast (void (%class.Base*)* @_ZN4BaseD0Ev to i8*)] }, align 8
@_ZTV8Derived1 = linkonce_odr unnamed_addr constant { [6 x i8*] } { [6 x i8*] [i8* null, i8* bitcast ({ i8*, i8*, i8* }* @_ZTI8Derived1 to i8*), i8* bitcast (void (%class.Derived1*)* @_ZN8Derived14showEv to i8*), i8* bitcast (void (%class.Derived1*)* @_ZN8Derived18showcaseEv to i8*), i8* bitcast (%class.Derived1* (%class.Derived1*)* @_ZN8Derived1D1Ev to i8*), i8* bitcast (void (%class.Derived1*)* @_ZN8Derived1D0Ev to i8*)] }, align 8
@_ZTS8Derived1 = linkonce_odr hidden constant [10 x i8] c"8Derived1\00", align 1
@_ZTI8Derived1 = linkonce_odr hidden constant { i8*, i8*, i8* } { i8* bitcast (i8** getelementptr inbounds (i8*, i8** @_ZTVN10__cxxabiv120__si_class_type_infoE, i64 2) to i8*), i8* inttoptr (i64 add (i64 ptrtoint ([10 x i8]* @_ZTS8Derived1 to i64), i64 -9223372036854775808) to i8*), i8* bitcast ({ i8*, i8* }* @_ZTI4Base to i8*) }, align 8

其中3个类型的virtual table定义分别如下,同时虚表保存的都是 i8* 指针,函数指针也会被 casti8*

  • Base: @_ZTV4Base = [null, @_ZTI4Base, @_ZN4Base4showEv, @_ZN4Base8showcaseEv, @_ZN4BaseD1Ev, @_ZN4BaseD0Ev]

  • Derived: @_ZTV7Derived = [null, @_ZTI7Derived, @_ZN7Derived4showEv, @_ZN7Derived8showcaseEv, @_ZN7DerivedD1Ev, @_ZN7DerivedD0Ev]

  • Derived1: @_ZTV8Derived1 = [null, @_ZTI8Derived1, @_ZN8Derived14showEv, @_ZN8Derived18showcaseEv, @_ZN8Derived1D1Ev, @_ZN8Derived1D0Ev]

接下来看看构造函数,一般来说构造函数包含了初始化virtual pointer的代码,不过3个类的文本有点多,而且重复,这里放下 Derived 类的构造函数,其构造函数包含 C1C2

; Function Attrs: noinline optnone ssp uwtable
define linkonce_odr noundef %class.Derived* @_ZN7DerivedC1Eii(%class.Derived* noundef nonnull returned align 8 dereferenceable(24) %this, i32 noundef %aa, i32 noundef %bb) unnamed_addr #2 align 2 {
entry:
  %this.addr = alloca %class.Derived*, align 8
  %aa.addr = alloca i32, align 4
  %bb.addr = alloca i32, align 4
  store %class.Derived* %this, %class.Derived** %this.addr, align 8
  store i32 %aa, i32* %aa.addr, align 4
  store i32 %bb, i32* %bb.addr, align 4
  %this1 = load %class.Derived*, %class.Derived** %this.addr, align 8
  %0 = load i32, i32* %aa.addr, align 4
  %1 = load i32, i32* %bb.addr, align 4
  %call = call noundef %class.Derived* @_ZN7DerivedC2Eii(%class.Derived* noundef nonnull align 8 dereferenceable(24) %this1, i32 noundef %0, i32 noundef %1)
  ret %class.Derived* %this1
}

; Function Attrs: noinline optnone ssp uwtable
define linkonce_odr noundef %class.Derived* @_ZN7DerivedC2Eii(%class.Derived* noundef nonnull returned align 8 dereferenceable(24) %this, i32 noundef %aa, i32 noundef %bb) unnamed_addr #2 align 2 {
entry:
  %this.addr = alloca %class.Derived*, align 8
  %aa.addr = alloca i32, align 4
  %bb.addr = alloca i32, align 4
  store %class.Derived* %this, %class.Derived** %this.addr, align 8
  store i32 %aa, i32* %aa.addr, align 4
  store i32 %bb, i32* %bb.addr, align 4
  %this1 = load %class.Derived*, %class.Derived** %this.addr, align 8
  %0 = bitcast %class.Derived* %this1 to %class.Base*
  %1 = load i32, i32* %aa.addr, align 4
  %2 = load i32, i32* %bb.addr, align 4
  %call = call noundef %class.Base* @_ZN4BaseC2Eii(%class.Base* noundef nonnull align 8 dereferenceable(16) %0, i32 noundef %1, i32 noundef %2)
  %3 = bitcast %class.Derived* %this1 to i32 (...)***
  store i32 (...)** bitcast (i8** getelementptr inbounds ({ [6 x i8*] }, { [6 x i8*] }* @_ZTV7Derived, i32 0, inrange i32 0, i32 2) to i32 (...)**), i32 (...)*** %3, align 8
  ret %class.Derived* %this1
}

忽略大部分没什么语义的 loadstore 指令。可以看到 C2 倒数几行存在代码

%3 = bitcast %class.Derived* %this1 to i32 (...)***
store i32 (...)** bitcast (i8** getelementptr inbounds ({ [6 x i8*] }, { [6 x i8*] }* @_ZTV7Derived, i32 0, inrange i32 0, i32 2) to i32 (...)**), i32 (...)*** %3, align 8

简化下就是 %a = getelementptr @_ZTV7Derived, 2store %a, %3。将 this 指针指向的地址开头部分初始化为 @_ZTV7Derived 偏移2处指向的地址(指向虚表第一个函数)。

main 函数IR文本如下,其中 @_Znwm 函数用来执行 new 运算符,分配堆内存:

; Function Attrs: mustprogress noinline norecurse optnone ssp uwtable
define noundef i32 @main(i32 noundef %argc, i8** noundef %argv) #0 personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) {
entry:
  %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  %basePtr = alloca %class.Base*, align 8
  %exn.slot = alloca i8*, align 8
  %ehselector.slot = alloca i32, align 4
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %0 = load i32, i32* %argc.addr, align 4
  %cmp = icmp eq i32 %0, 1
  br i1 %cmp, label %if.then, label %if.else

if.then:                                          ; preds = %entry
  %call = call noalias noundef nonnull i8* @_Znwm(i64 noundef 24) #6
  %1 = bitcast i8* %call to %class.Derived*
  %call1 = invoke noundef %class.Derived* @_ZN7DerivedC1Eii(%class.Derived* noundef nonnull align 8 dereferenceable(24) %1, i32 noundef 10, i32 noundef 20)
          to label %invoke.cont unwind label %lpad

invoke.cont:                                      ; preds = %if.then
  %2 = bitcast %class.Derived* %1 to %class.Base*
  store %class.Base* %2, %class.Base** %basePtr, align 8
  br label %if.end

lpad:                                             ; preds = %if.then
  %3 = landingpad { i8*, i32 }
          cleanup
  %4 = extractvalue { i8*, i32 } %3, 0
  store i8* %4, i8** %exn.slot, align 8
  %5 = extractvalue { i8*, i32 } %3, 1
  store i32 %5, i32* %ehselector.slot, align 4
  call void @_ZdlPv(i8* noundef %call) #7
  br label %eh.resume

if.else:                                          ; preds = %entry
  %call2 = call noalias noundef nonnull i8* @_Znwm(i64 noundef 24) #6
  %6 = bitcast i8* %call2 to %class.Derived1*
  %call5 = invoke noundef %class.Derived1* @_ZN8Derived1C1Eii(%class.Derived1* noundef nonnull align 8 dereferenceable(24) %6, i32 noundef 10, i32 noundef 20)
          to label %invoke.cont4 unwind label %lpad3

invoke.cont4:                                     ; preds = %if.else
  %7 = bitcast %class.Derived1* %6 to %class.Base*
  store %class.Base* %7, %class.Base** %basePtr, align 8
  br label %if.end

lpad3:                                            ; preds = %if.else
  %8 = landingpad { i8*, i32 }
          cleanup
  %9 = extractvalue { i8*, i32 } %8, 0
  store i8* %9, i8** %exn.slot, align 8
  %10 = extractvalue { i8*, i32 } %8, 1
  store i32 %10, i32* %ehselector.slot, align 4
  call void @_ZdlPv(i8* noundef %call2) #7
  br label %eh.resume

if.end:                                           ; preds = %invoke.cont4, %invoke.cont
  %11 = load %class.Base*, %class.Base** %basePtr, align 8
  %12 = bitcast %class.Base* %11 to void (%class.Base*)***
  %vtable = load void (%class.Base*)**, void (%class.Base*)*** %12, align 8
  %vfn = getelementptr inbounds void (%class.Base*)*, void (%class.Base*)** %vtable, i64 0
  %13 = load void (%class.Base*)*, void (%class.Base*)** %vfn, align 8
  call void %13(%class.Base* noundef nonnull align 8 dereferenceable(16) %11)
  %14 = load %class.Base*, %class.Base** %basePtr, align 8
  call void @_ZN4Base7displayEv(%class.Base* noundef nonnull align 8 dereferenceable(16) %14)
  %15 = load %class.Base*, %class.Base** %basePtr, align 8
  %isnull = icmp eq %class.Base* %15, null
  br i1 %isnull, label %delete.end, label %delete.notnull

delete.notnull:                                   ; preds = %if.end
  %16 = bitcast %class.Base* %15 to void (%class.Base*)***
  %vtable6 = load void (%class.Base*)**, void (%class.Base*)*** %16, align 8
  %vfn7 = getelementptr inbounds void (%class.Base*)*, void (%class.Base*)** %vtable6, i64 3
  %17 = load void (%class.Base*)*, void (%class.Base*)** %vfn7, align 8
  call void %17(%class.Base* noundef nonnull align 8 dereferenceable(16) %15) #8
  br label %delete.end

delete.end:                                       ; preds = %delete.notnull, %if.end
  ret i32 0

eh.resume:                                        ; preds = %lpad3, %lpad
  %exn = load i8*, i8** %exn.slot, align 8
  %sel = load i32, i32* %ehselector.slot, align 4
  %lpad.val = insertvalue { i8*, i32 } undef, i8* %exn, 0
  %lpad.val8 = insertvalue { i8*, i32 } %lpad.val, i32 %sel, 1
  resume { i8*, i32 } %lpad.val8
}

其中 basePtr->show 对应的IR为:

%11 = load %class.Base*, %class.Base** %basePtr, align 8
%12 = bitcast %class.Base* %11 to void (%class.Base*)***
%vtable = load void (%class.Base*)**, void (%class.Base*)*** %12, align 8
%vfn = getelementptr inbounds void (%class.Base*)*, void (%class.Base*)** %vtable, i64 0
%13 = load void (%class.Base*)*, void (%class.Base*)** %vfn, align 8
call void %13(%class.Base* noundef nonnull align 8 dereferenceable(16) %11)

非虚函数调用如下,可以看出是个direct-call,需要传入 basePtrthis 指针。

%14 = load %class.Base*, %class.Base** %basePtr, align 8
call void @_ZN4Base7displayEv(%class.Base* noundef nonnull align 8 dereferenceable(16) %14)

2.识别virtual call

这里主要参考SVF和LLVM相关代码。在source code层面识别virtual call可参考CSA VirtualCallChecker,不过在LLVM IR层面,由于virtual-call编译后成了indirect-call。从普通indirect-call中区分成virtual-call便有些困难。这里参考了下SVF的相关代码,对应代码为cppUtil::isVirtualCallSite函数。

分析的核心insight在于virtual-call指令的相关IR一定满足以下格式:

%vtable = load this
%vfn = getelementptr %vtable, idx
%x = load %vfn
call %x (this)

因此SVF的识别算法便按照以下方式工作:

  • 对于间接调用 call %x (this),判断 %x 是不是在 load 指令定义,如果不是,返回 false

  • 对于 load 指令 %x = load %vfn,判断 %vfn 是不是在 getelementptr 指令定义,不是返回 false

  • 对于 %vfn = getelementptr %vtable, idx,由于virtual table一般是 i8* 类型的一位数组,因此该指令只能有1个索引,如果有多个索引返回 false。同时 %table 如果是由 load 指令定义,返回 true,否则返回 false

3.CHA算法

分析virtual-call的一个常用算法是CHA(类型层次分析),就是把callsite this 指针的类型及其子类型的实现都当作潜在调用目标。C++的virtual call分析一个常用算法是CHA,即对于一个基类指针的虚函数调用,将所有子类实现都当作潜在调用目标。实现时对于virtual callsite,需要考虑:1.当前callsite调用了哪些类型的implementation。2.每个类型的virtual table对应哪些函数

3.1.callsite到类型的map

SVF在分析callsite对应的类型时并不是粗暴的直接递归分析类型继承图确定virtual-call调用目标。而是做了个简单的数据流分析确定 base 指针可能指向了哪些构造函数初始化的object来确定调用目标。数据流分析包括backward和forward分析,以上图为例:

if.then:                                          ; preds = %entry
  %call = call noalias noundef nonnull i8* @_Znwm(i64 noundef 24) #6
  %1 = bitcast i8* %call to %class.Derived*
  %call1 = invoke noundef %class.Derived* @_ZN7DerivedC1Eii(%class.Derived* noundef nonnull align 8 dereferenceable(24) %1, i32 noundef 10, i32 noundef 20)
          to label %invoke.cont unwind label %lpad

invoke.cont:                                      ; preds = %if.then
  %2 = bitcast %class.Derived* %1 to %class.Base*
  store %class.Base* %2, %class.Base** %basePtr, align 8

%11 = load %class.Base*, %class.Base** %basePtr, align 8
%12 = bitcast %class.Base* %11 to void (%class.Base*)***
%vtable = load void (%class.Base*)**, void (%class.Base*)*** %12, align 8
%vfn = getelementptr inbounds void (%class.Base*)*, void (%class.Base*)** %vtable, i64 0
%13 = load void (%class.Base*)*, void (%class.Base*)** %vfn, align 8
call void %13(%class.Base* noundef nonnull align 8 dereferenceable(16) %11)

value-flow graph为:

indirect
%call= alloca
%1
%2
%call2 = constructor
store %2, basePtr
%11 = load basePtr

这里只列出if-then部分的IR。virtual-call为 call void %13(%class.Base* noundef nonnull align 8 dereferenceable(16) %11)this 指针为 %11,从 %11 开始一路回溯经由 %12 = bitcast 后找到 load 指令 %11 = load %class.Base*, %class.Base** %basePtr, align 8,这里不会进行指针分析,而是找出所有直接往 %basePtr 进行写入的 store 指令进行回溯,这里找到了 store %class.Base* %2, %class.Base** %basePtr, align 8,接着从 %2 接着回溯知道调用堆分配函数 %call = alloca_Znwm 只分配内存,初始化还得看构造函数)。

如果要回溯到构造函数调用还得从 %call = alloca 处forward分析,在backward分析时SVF用了worklist算法。forward分析时,由于基本上从内存分配到构造函数调用中间最多有一个 bitcast 指令,所以SVF直接用一个简单的判断从 %call = alloca 查找构造函数调用,找到后从函数名中回复出类型名。上述virtual-call分析出调用了 DerivedDerived1 的implementation。

对于SVF中对外部API建模在extapi.c中,可以看到 _Znwm 被建模为 alloc 函数。对于从virtual table等 name中恢复出类型名SVF有一套具体规则,以virtual table为例,SVF用到了 abi::__cxa_demangle 来从virtual table名字还原出对应类型,比如对于 vtblName = _ZTV7Derived,其 abi::__cxa_demangle(vtblName.c_str(), 0, 0, &status) = "vtable for Derived",去掉 "vtable for " 即可获得类型名。

3.2.获取调用目标

每个类都会有一个虚函数表,SVF会分析类虚函数表对应的全局变量(_ZTV 开头)构建functionVector。比如 @_ZTV7Derived = [null, @_ZTI7Derived, @_ZN7Derived4showEv, @_ZN7Derived8showcaseEv, @_ZN7DerivedD1Ev, @_ZN7DerivedD0Ev] 会分析出 Derived 类的 functionVector{ @_ZN7Derived4showEv, @_ZN7Derived8showcaseEv, @_ZN7DerivedD1Ev, @_ZN7DerivedD0Ev }。具体代码参考CHGBuilder::analyzeVTables函数,其中代码由于涉及处理多重继承逻辑更加复杂。

获得每个类的 functionVector 后接下来要确定virtual call的虚表索引,上面示例中 basePtr->show(); 的部分LLVM IR如下,可以看出对应虚表索引为 0

%vtable = load void (%class.Base*)**, void (%class.Base*)*** %12, align 8
%vfn = getelementptr inbounds void (%class.Base*)*, void (%class.Base*)** %vtable, i64 0
%13 = load void (%class.Base*)*, void (%class.Base*)** %vfn, align 8
call void %13(%class.Base* noundef nonnull align 8 dereferenceable(16) %11)

由于virtual-call满足以下格式,因此SVF按照cppUtil::getVCallIdx从 call 指令处追溯到 getelementptr 指令获得虚表索引,接着就可以用索引访问所有相关类的 functionVector 获取调用函数。

%vtable = load this
%vfn = getelementptr %vtable, idx
%x = load %vfn
call %x (this)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2146671.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

互联网前端之 CSS 常见属性与三层结构

目录 现在互联网前端三层 CSS 常见属性 关注作者微信公众号,开启探索更多 CSS 知识的精彩之旅。在这里,你将收获丰富的CSS专业内容,深入了解这一网页开发语言的奥秘,不断拓展你的知识边界,提升技能水平。快来关注吧&…

对想学习人工智能或者大模型技术从业者的建议,零基础入门到精通,收藏这一篇就够了

“ 技术的价值在于应用,理论与实践相结合才能事半功倍**”** 写这个关于AI技术的公众号也有差不多五个月的时间了,最近一段时间基本上都在保持日更状态,而且写的大部分都是关于大模型技术理论和技术方面的东西。‍‍‍‍‍‍‍‍‍ 然后最近…

Windows安全日志分析(事件ID详解)

目录 如何查看Windows安全日志 常见事件ID列表 事件ID 1116 - 防病毒软件检测到恶意软件 事件ID 4624 - 账户登录成功 事件ID 4625 - 账户登录失败 事件ID 4672 - 为新登录分配特殊权限 事件ID 4688 - 新进程创建 事件ID 4689 - 进程终止 事件ID 4720 - 用户账户创建 …

3款免费的GPT类工具

前言 随着科技的飞速发展,人工智能(AI)的崛起与发展已经成为我们生活中不可或缺的一部分。它的出现彻底改变了我们与世界互动的方式,并为各行各业带来了前所未有的便利。 一、Kimi 网址:点我前往 国产AI模型Kimi是一…

C++第八节课 日期类的补充

在上节课我们提交的代码中,还有着一些不足: 如果我们想要运行下面的函数: void test4() {Date d1(2023, 5, 5);d1 -50;d1.Print();Date d2(2023, 5, 5);d2 - -50;d2.Print(); } 我们发现之前的代码没有考虑day为负数的情况,可以…

浅谈红外测温技术在变电站运维中的应用

0引言 随着市场经济的繁荣发展,社会对电力的需求持续增长。城市供电网络的规模和用电设备的总量也在不断扩大,这导致城市电力系统中潜在的网络安全隐患日益增多。作为电力系统核心组成部分的变压器,其安全、稳定的工作直接关系到电能的质量和…

俄罗斯的Alexey V. Gubin开发的数据恢复文件-零假设恢复只读模式下对扫描/恢复数据起作用-供大家学习研究参考

// 主要特征 // Windows FAT,NTFS,Linux ext2 / 3/4和XFS卷格式化的驱动器或“ RAW文件系统”外部驱动器,USB拇指驱动器和存储卡带有ZAR Data Recovery免费版本的数码照片恢复RAID数据恢复NAS数据恢复MBR损坏数据恢复具有多个逻辑驱动器的分区表恢复支持长文件名和国家文件名…

图神经网络模型应用(8)--2

1.自然语言处理相关的应用 文本分类是自然语言处理中的一个经典应用,图神经网络常用的标准数据集里就包含引用网络中论文的分类,但是作为机器学习领域的通用模型测试数据集,它们并没有充分利用文本本身的结构(每个文档只是用词袋特征来表示),…

【微处理器系统原理和应用设计第十五讲】模拟/数字转换器

一、基础概念 1、概念 ADC将真实世界的例如温度、压力、声音或者图像等的模拟信号转换为更容易储存、处理和发射的数字形式。 2、分类 间接ADC:先将输入模拟电压转化为时间/频率等数字信号,例如双积分型ADC 直接ADC:直接转化为数字量&am…

Pandas的读写数据

目录 读写文件的类型 Excel写 API 准备数据 1.直接写入(默认有索引和标题) 2.写入(去掉索引) 3.写入(去掉索引和标题) 4.写入(去掉索引和标题,指定表单信息) Excel读 API 1.读(默认带有索引和标题) 2.读(指定索引项) 3.读(碰到无标题列和无索引列,指定索引列,标题列…

C++速通LeetCode中等第7题-和为K的子数组(巧用前缀和)

巧用哈希表与前缀和&#xff0c;前缀和差为k的两个序号之间的数组就是满足条件的子数组&#xff0c;用哈希表来存放每个序号的前缀和。 前缀和就是头元素到当前序号子数组元素的和 class Solution { public:int subarraySum(vector<int>& nums, int k) {unordered_…

【软件测试】测试的岗位有哪些?

求职入口有很多&#xff1a;相关企业官网、求职软件、校招、公众号等等。 下面就在某招聘网站上看看测试有哪些岗位吧&#xff01; 测试只是一个统称&#xff0c;在测试下面还有很多细分岗位。 但是测试的岗位主要分为以下俩个方面&#xff1a; 软件测试开发工程师&#xff…

Linux(Centos7)系统下给已有分区进行扩容

本文详细介绍了&#xff0c;如何给Centos中已有分区进行扩容&#xff0c;简单的几条命令即可完成。 文章目录 1. 创建物理卷 (PV)2. 将新的物理卷添加到卷组 (VG)3. 扩展逻辑卷 (LV)4. 扩展文件系统4.1 查看文件系统类型4.2 扩展文件系统 完成 1、首先把vmware中的linux关机&am…

【Python】从基础到进阶(九):探索Python中的迭代器与生成器

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、引言二、迭代器1. 什么是迭代器&#xff1f;迭代器的工作流程&#xff1a; 2. 使用内置迭代器3. 自定义迭代器 三、生成器1. 什么是生成器&#xff1f;2. 创建生成器3. 生成器表达式 四、生成器与迭代器的区别五、生成器…

Python基础(六)——PyEcharts数据可视化初级版

案例 【前言&#xff1a;为了巩固之前的Python基础知识&#xff08;一&#xff09;到&#xff08;五&#xff09;&#xff0c;并为后续使用Python作为数据处理的好帮手&#xff0c;我们一起来看几个例子】 使用工具&#xff1a;Echarts Echarts 是一个由百度开源的数据可视化…

Node js介绍

目录 概要**对Node的认识****Node的概念理解****Node和浏览器区别****Node的架构图** **Node的应用场景****Node的安装****安装Node的LTS版本****Node的版本管理工具nvm(了解)** **Node的输入和输出**Node程序传递参数Node的输出 **Node的全局对象****特殊的全局对象****其他的…

C++类与对象深度解析(五):友元机制、内部类与匿名对象的高级应用

文章目录 C类和对象——全面指南前言4. 友元详解4.1 友元的基本概念 4.2 友元函数示例代码&#xff1a;友元函数访问两个类的私有成员输出&#xff1a; 4.3 友元类示例代码&#xff1a;友元类的使用输出&#xff1a; 4.4 友元的特性与限制4.5 友元函数与类的实际应用示例&#…

WAAP解决方案:守护数字时代的安全盾牌

在当今这个数字化、数据驱动的时代&#xff0c;网络安全已成为企业运营中不可或缺的一环。随着Web应用程序和API接口在业务中的广泛应用&#xff0c;其面临的安全威胁也日益复杂多变。为此&#xff0c;WAAP&#xff08;Web Application and API Protection&#xff09;解决方案…

JavaEE:网络编程(UDP)

文章目录 UDPUDP的特点UDP协议端格式校验和前置知识校验和具体是如何工作的? UDP UDP的特点 UDP传输的过程类似于寄信. 无连接: 知道对端的IP和端口号就直接进行传输,不需要建立连接.不可靠: 没有确认机制,没有重传机制,如果因为网络故障导致该段无法到达对方,UDP协议也不会…

字符函数 和 字符串函数 的使用与模拟

前言&#xff1a;在编程的过程中&#xff0c;我们经常要处理字符和字符串&#xff0c;为了⽅便操作字符和字符串&#xff0c;C语⾔标准库中提供了 ⼀系列库函数&#xff0c;接下来我们就学习⼀下这些函数。 目录 1. 字符函数 1.1 字符分类判断函数 1.2 字符转换函数 1.3 练…