当前位置: 首页 > news >正文

对一个变速器原理的分析

背景

原本是朋友在调试一个看起来比较新的变速器驱动,整体来说支持两种变速模式,一种是进程级,这种用了HOOK,中规中矩的实现,原理网上都有。另一种是”系统级内核全局变速“,这个模式初步看了下有些特殊,已知的关键点没被修改,也没hook。比较好奇是怎么实现的,花了几天时间分析,也有一些有意思的地方,发个文章记录一下。

一,KeQueryPerformanceCounter

写了个简单的驱动,直接调用KeQueryPerformanceCounter会被加速,那么从这里入手应当没问题。
KeQueryPerformanceCounter网上其他相关文章或多或少都有涉及,只写一下关键调用路径:

1

2

3

4

5

KeQueryPerformanceCounter

    HalpPerformanceCounter + 0x70;

        HalpHvCounterQueryCounter

            HalpHvTimerApi

            HvlGetReferenceTimeUsingTscPage

 
核心逻辑在最后一层的HvlGetReferenceTimeUsingTscPage中,主要是读__rdtsc()然后做一些运算:
 

1

2

3

4

5

6

7

8

9

10

11

12

13

__int64 __fastcall HvlGetReferenceTimeUsingTscPage(int a1, __int64 a2)

{

        v2 = __rdtsc();

        LODWORD(a2) = HIDWORD(v2);

        v2 = (unsigned int)v2;

        a2 = (unsigned int)a2;

        a2 = *((_QWORD *)HvlpReferenceTscPage + 2)

           + (((v2 | (a2 << 32)) * (unsigned __int128)*((unsigned __int64 *)HvlpReferenceTscPage + 1)) >> 64);

        v5 = a2;

        a1 = *(_DWORD *)HvlpReferenceTscPage;

        if ( *(_DWORD *)HvlpReferenceTscPage == v3 )

        return v5;

}

 
没开嵌套虚拟化,所以rdtsc肯定没被动手脚,调用链里涉及到的相关函数指针及代码确实都没修改。为了缩小范围及进一步排除,跳过前面几层,直接调用HvlGetReferenceTimeUsingTscPage,甚至把代码抠出来直接执行也是被加速,那么猫腻一定在这段代码里面,对其逻辑做一些分析简化:
 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

struct HvRTP

{

    uint32_t unk1;

    uint32_t unk2;

    uint64_t factor;

    uint64_t unk3;

    //........

};

//上面IDA伪代码中的HvlpReferenceTscPage大致就是上面这么一个结构体指针, 核心逻辑简化后等价于:

__int64 __fastcall HvlGetReferenceTimeUsingTscPage()

{

        count = HvlpReferenceTscPage->unk3 + (__rdtsc() * HvlpReferenceTscPage->factor) >> 64;

        return count;

}

 
unk3固定是0,那就等价于:count = (__rdtsc() * HvlpReferenceTscPage->factor) >> 64;
排除hook以及rdtsc,剩下唯一的可能就是改变了factor(HvlpReferenceTscPage + 8)。

 
多次观察验证:

  • 在不启动变速功能时,factor值一直不变,当开启加速后,factor会被修改,如果停止变速,factor值就会被还原;
  • factor变化规律与加速倍率直接相关,如果调1.2倍,样本驱动就会把factor改为近似于原值*1.2,变速软件本身限制最高倍率1.5,但手动eq将factor改为原值的10倍后,系统整体就会表现出10倍速效果。
     

看起来很简单,但实际上分析才刚刚开始。

二,蓝屏

当朋友按照这个结论去测试时,发现HvlpReferenceTscPage+8根本就没法改,可以读,但只要写入就会发生WHEA_UNCORRECTABLE_ERROR(0x124)蓝屏,我最初一直认为是写内存方式不对,但是朋友最后发现了一些规律:

  • 几乎所有写入方式,比如常用的mdl/MmGetPhyAddr+map,或者关WP位,包括windbg的eq无一例外全部都会导致0x124蓝屏;
  • !pte检查map后的页属性没问题;
  • 变速样本始终可以稳定运行,而且只要通过变速样本开启一次加速,即使停止加速后,随便怎么写都不会触发蓝屏。
  • 蓝屏只在HyperV环境出现

 
当我也切换为HyperV,果然出现0x124蓝屏:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

8: kd> k

 # Child-SP          RetAddr               Call Site

00 ffffbf81`3a347918 fffff807`276f7adb     nt!KeBugCheckEx

01 ffffbf81`3a347920 fffff807`24db1740     nt!HalBugCheckSystem+0xeb

02 ffffbf81`3a347960 fffff807`27826c93     PSHED!PshedBugCheckSystem+0x10

03 ffffbf81`3a347990 fffff807`276f9411     nt!WheaReportHwError+0x393

04 ffffbf81`3a347a60 fffff807`276f9858     nt!HalpMcaReportError+0xb1

05 ffffbf81`3a347bc0 fffff807`276f96f0     nt!HalpMceHandlerCore+0x134

06 ffffbf81`3a347c20 fffff807`276f9922     nt!HalpMceHandler+0xe0

07 ffffbf81`3a347c60 fffff807`276f8bd6     nt!HalpMceHandlerWithRendezvous+0x62

08 ffffbf81`3a347c90 fffff807`276fb4fb     nt!HalpHandleMachineCheck+0x62

09 ffffbf81`3a347cc0 fffff807`27756e39     nt!HalHandleMcheck+0x3b

0a ffffbf81`3a347cf0 fffff807`276281be     nt!KiHandleMcheck+0x9

0b ffffbf81`3a347d20 fffff807`27627de8     nt!KxMcheckAbort+0x7e

0c ffffbf81`3a347e60 fffff807`430a72eb     nt!KiMcheckAbort+0x2a8

从蓝屏栈可以看到是写内存时发生MCE异常,VBS没开而且也不像是VBS的表现,应当是hypervisor发现某些异常后,主动向虚拟机内注入的MCE,触发原因可能是EPT物理页属性只读。
 
理论上有一种简单的解决办法:不要直接改HvlpReferenceTscPage+8,而是自己分配一块内存,然后把指针替换到HvlpReferenceTscPage。但样本驱动不是这么干的,应当有它的原因,比如PG或者为了隐蔽?具体什么原因不重要,最关键同时也最让我好奇的是,如果真的是因为EPT页只读触发的蓝屏,那样本驱动是怎么让页变成可写的?

 
正面硬刚VMP是下策,所以首先尝试的思路是对常用内存相关函数下断点,没看出变速驱动有什么特殊操作,就是普普通通的IoAllocateMDL + Map,传的参数与自己的测试代码也是一模一样。 那么这个驱动在写内存前,必然还有其他操作在配合。此时要么直面VMP,要么调试HyperV,没找到能用的VMP插件,调HyperV现实一点。

三, Hyper-V

首先验证猜测,确认是不是因为EPT不可写而注入的MCE:

  1. 这里本该有图,不过写这个贴子的时候,距离分析已经有一段时间了,懒得再重新搭建调试环境,所以写一下关键思路和过程
  2. 通过搜索0x4402/0x6400定位HyperV的vcpu_run_loop()及vmexit_dispatch()
  3. 梳理vmexit_dispatch()内针对每种exit_reason的处理函数(重点是EPT退出)
  4. 在ept_vio_exit_handler内合适位置下条件断点,条件设置为vmread(0x2400h)=GPA(HvlpReferenceTscPage)
  5. guest内尝试写入HvlpReferenceTscPage+8;
  6. 断点会命中,根据vmread(0x6400)的信息或者手动转换也能发现对应页面只有读权限,再往后执行就会给子机注入MCE异常;
  7. 重启子机,继续下第3步的断点
  8. 加载变速驱动开启变速功能,断点不会命中也不会蓝屏,但对应内存已改写;随便在vcpu_run路径上下个断点,当vcpu退出命中断点,根据EPTP推一遍,会发现对应页已经是可读写;

可以证实MCE蓝屏确实是因为物理页不可写导致,接下来的关键问题是要分析变速驱动是如何让GPA从只读变成可读写的。
 
理论上在没有通过EFI等方式Patch hvix64.exe的情况下,要从Guest内实现改变EPTP内的GPA属性就三种方式:

  • 第一种是通过vmfunc(0,)切换EPTP, 这种一般不会产生vmexit,windows内部也确实会用到,但通过断点验证可以确定当时没执行过,或者变速驱动自己调用了vmfunc(0,),通过指令搜索方式也基本上可以排除掉,VMP后vmfunc这类指令还是以原来的形式存在,在对应代码段里并不能搜索到这条指令;
  • 第二种是变速驱动在guest里做了什么操作,促使hypervisor修改了对应的页面属性。这类方式首先想到的是HyperV是不是有什么VMCall,但是在VMCall的exit_handler下断点并没有什么线索;
  • 第三种是bug,可能性太小。。。。

验证完自己能想到的两种方式但没有线索后,只能回到标准思路:

  • 先定位HyperVisor修改页面属性的代码,然后设置条件断点判断是不是在修改HvlpReferenceTscPage所对应的GPA页属性,如果断点触发,结合host/guest当时的栈去分析。

这个思路肯定行得通,但是仔细想一下,vmfunc(0)这种唯一无VMExit的方式可以排除,那么即使想不到它具体用的什么方式,也可以确定过程中一定会产生vmexit。
 
所以最终我用了一个偷懒的思路,统计未开启和开启加速时的vmexit事件,对比分析差异:

  • 在vcpu_run_loop()函数内,vmread(0x4402)读取到exit_reason之后的位置下断点自动trace虚拟机的vmexit信息(主要记录reason和guest_rip);
  • 对比变速前后的vmexit统计信息,如果发现某些vmexit仅在变速驱动开启加速的过程中才出现,那么这些vmexit事件就很可能是突破口;

反复跑几遍,最后发现的规律是:

  • 变速驱动在开启变速,改写HvlpReferenceTscPage+8之前,一定有几次rd/wrmsr,而这些msr在正常运行过中系统从不访问,并且通过vmexit时的guest_rip可以确认这些rd/wrmsr都是由变速驱动直接执行。

接下来就是具体分析这些rd/wrmsr,直接在guest_rip或者在HyperV这边的rd/wrmsr_exit_handler处下断点,再观察rd/wrmsr执行前/后VCPU ecx,edx,eax的值,就可以知道读写了哪些寄存器,以及读写的具体数据。

四, MSR 0x40000021

在HyperV的wrmsr_exit_handler处下断点,多次观察后首先发现这样一个规律:

  1. 开机后第一次开启加速时,变速驱动会读取MSR 0x40000021, 此时读取到的值为0xC001
  2. 变速驱动对这个MSR 写入0xC000(仅在第一次开启加速时才会写入)
  3. 后续开启加速,每次都会读但不会再写入(每次读取到的值也都是上一步中写入的0xC000);
     

如果只是读MSR倒也没什么,但写MSR比较可疑。搜一下MSR 0x40000021,找到这样两个相对比较有用的文档:

  1. https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/tlfs/timers。
  2. Hyper-V Enlightenments — QEMU documentation

综合这些文档,可以知道以下关键信息:

  1. 0x40000021是HyperV自己定义的MSR,相当于一个接口,用于GuestOS与HyperV协商初始化Reference TSC page;
  2. Reference TSC page是为了给GuestOS提供一个访问时不会产生vmexit的时间戳计数器;
  3. MSR对应的读写数据格式为:bit 0表示是否启用Reference TSC page,bit 12-63表示GPA PageNumber,其他都是保留位;
  4. 默认情况下Reference TSC page功能是禁用状态,值为0,开机启动过程中由GuestOS主动写入这个MSR才能启用;

结合这些信息和上面所说的变速驱动读写0x40000021的规律,可以进一步明确其每一步的含义:

  1. 第一次读取rdmsr(0x40000021)返回的是0xC001:
    • 解析0xC001:
      • bit 0为1,那就说明此时GuestOS与HyperV已经协商并初始化Reference TSC page
      • 对应GPA PageNumber为0xC,对应GPA为0xC*0x1000=0xC000
  2. 变速驱动对这个MSR写入0xC000:
    • 0xC000就是0xC001清掉bit 0,其他位保持原样;bit 0表示是否启用,那么变速驱动这一步就是通过这个MSR接口,通知HyperV禁用掉Referenct TSC page;

此外还有一个最最关键的信息:

  • 上面提到“0xC001对应的GPA=0xC*0x1000=0xC000”,这个0xC000其实就是HvlpReferenceTscPage的GPA,并且也正是发生EPT写入异常最终导致MCE蓝屏的GPA

结合推测,变速驱动禁用reference tsc page的目的,很有可能是禁用后HyperV就会在EPT中将对应Page的从只读变为读写。
 
调整测试代码,在修改内存前也加上这个操作,终于可以顺利写入HvlpReferenceTscPage;同时也在HyperV这边进行验证:

  • 在wrmsr_exit_handler_0x40000021处下断点,断点触发时,确认0xC这个页面在EPT中属性为只读,随后step out,让HyperV处理完这次wrmsr,当再次中断到Windbg时,0xC页面就变为了可读写。此时尚未执行VMResume,虚拟机又是单核,VCPU退出后下一次VMResume前等同于是停转状态,这期间GuestOS不可能再有任何操作,也不可能再有其他vmexit,因此可以断定GPA从只读变为可写,100%是由wrmsr(0x40000021,0xc000)触发。

五,系统假死

解决这个页面写入问题之后,再次遇到新的问题:

  • 如果将factor改为原来的10倍,虚拟机会假死,而通过变速样本驱动进行10倍加速后系统依旧正常运行

原因是GuestOS内确实变速了,但Hypervisor这边没变,而GuestOS是将时钟设备配置为OneShot模式,在这种模式下,写到时钟设备的“到期时间”是一个绝对时间,这个绝对到期时间的计算方法是:HalpHvCounterQueryCounter() + ClockInterval,这里的HalpHvCounterQueryCounter其实等同于调用KeQueryPerfCounter(),所获取到的Counter值是10倍加速后的结果,而时钟设备是由Hypervisor模拟,Hypervisor这边的时间是以正常速度流逝,所以最终时钟到期时间会越来越晚,对于GuestOS来说就是时钟中断被大幅延迟,最终DPC/线程调度全部跟着延迟,分时系统遇到这种情况必然假死。
 

这个结论的分析和验证过程如下:

  1. Hook SwapContext:发现调用次数相比正常时少了很多,线程切换调度肯定出了问题,看看DPC是否有问题
  2. 创建一个DPC Timer:实际执行时机相比预期时间被延迟而不是被加速,DPC Timer也有问题,那么继续看时钟中断是否有问题
  3. Hook HalpTimerClockInterrupt/KeClockInterruptNotify/KeUpdateRuntime:调用次数明显少于正常情况,再往下就是Hypervisor了,但不一定是Hypervisor导致,有可能是GuestOS配置的时钟周期有问题
  4. 结合IDA/Windbg,顺着时钟中断一路分析,发现在需要配置时钟中断到期时间时,最终都会经过以下路径:
    • HalpTimerClockArm->HalpSetTimer->HalpClockTimer+0x80()->HalpHvTimerArm()
  5. 在HalpHvTimerArm中,就会执行上面说的HalpHvCounterQueryCounter() + ClockInterval计算绝对到期时间,并配置到时钟设备,问题是这个时间是被加速后的
  6. 尝试Hook HalpHvTimerArm,将其从HalpHvCounterQueryCounter()获取到的值,重新还原回加速前的正常值,再写到时钟设备,假死问题消失

HOOK HalpClockTimer+0x80指向的HalpHvTimerArm确实也是一种解决办法,但样本驱动用了另一种方案:通过两个MSR接口,额外启动另一个时钟设备,并配置为period模式,周期为1ms。怎么发现的这两个MSR,绕了一圈最后还是通过VMEXIT统计对比发现的差异,变速样本驱动在写了0x40000021之后,会继续写0x400000B2/B3这两个MSR,只是VM运行过程中vmexit本身就比较频繁,这两个WRMSR退出事件和0x40000021中间还有一大堆其他vmexit,导致最初没注意到。

六,MSR 0x400000B2/B3

这两个MSR也是HyperV自己定义的,在前面的文档中一样有描述。主要用途其实跟LAPIC Timer差不多:

  • 0x400000B2 用于配置时钟模式,类似于LAPIC的LVTT(0x320)
    • 关键bit:
      • bit 0: Enable;是否启用;
      • bit 1: Period;是否为周期性时钟;
      • bit 3: AutoEnable;写入B3时是否自动启用Timer;
      • bit 4:11: Vector;时钟到期时,向GuestOS注入的中断Vector;
      • bit 12: DirectMode;如果为1, 时钟到期后通过标准的APIC向Guest注入中断通知子机,如果为0,则通过HyperV的SynIC注入中断通知子机;
  • 0x400000B3 用于配置时钟周期/到期事件,类似于LAPIC的TMICT(0x380)

变速驱动主要是执行wrmsr(0x400000B2,0x1D1A);wrmsr(0x400000B3,0x2710),其中0x1D1A表示周期性模式+自动启用+Vector D1+DirectMode,Vector D1对应Windows的HalpTimerClockInterrupt,0x2710=1ms。这样的话即使Win自己配置到时钟设备的绝对时间是加速后的,但是变速驱动额外启动了1ms周期性时钟用于给OS维持稳定的心跳,最终就不会卡死。
 
至此就知道关键流程:

  1. 对40000021 的bit 0清零,即禁用TSCRefPage,从而使HyperV将对应GPA从只读变为读写属性;
  2. 通过400000B2/B3提前配置好时钟,避免变速后系统假死;
  3. 改写factor实现变速;

按照这个逻辑,就可以复刻变速驱动的“全局系统级变速”模式。

七,其他

  1. 有心人可能会对0x40000021这个接口感兴趣,简单看了下应当不太好利用,因为当写入值bit 0为0时,wrmsr_handler_0x40000021其实并不是加上”W“权限,而是直接”恢复”原来的页映射。在HyperV里叫Overlay Page,简单理解就是贴一张新的A4纸到原来的纸上,如果要禁用,那就把贴上去的A4纸撕下来,将原来的A4纸原样“显示”出来,纸上的内容还是原来的样子。另外,虽然看起来可以通过0x40000021将任一Page贴到另一个Page之上,但是新Page权限以及Page中数据是由Hypervisor强制填充,即使自己先写好也会被清零。当然我没有深究,如果感兴趣的可以自己再调一下看看。
     

  2. 最后看一下(__rdtsc() * HvlpReferenceTscPage->factor) >> 64这个运算的意义及变速原理,Windows对factor的计算逻辑是:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    unsigned __int64 unk(__int64 a1, unsigned __int64 a2, unsigned __int64 a3, __int64 *a4)

    {

      __int64 v7; // rdi

      __int64 v8; // rcx

      __int64 v9; // r9

      __int64 v10; // rdx

      __int64 v11; // rbx

      v7 = 64i64;

      do

      {

        v8 = 2 * a2;

        v9 = (2 * a1) | (a2 >> 63);

        v10 = a1 >> 63;

        a1 = v9 - a3;

        v11 = 2 * a2;

        if ( (v10 | (unsigned __int64)v9) < a3 )

        a1 = v9;

        a2 = v11 | 1;

        if ( (v10 | (unsigned __int64)v9) < a3 )

        a2 = v8;

        --v7;

    }

    while ( v7 );

    if ( a4 )

        *a4 = a1;

    return a2;

    }

    传的参数是: unk(10000000i64, 0i64, 3.19xxxx, 0i64);

    其中10000000是代码逻辑中写死的QPC频率;3.19xxxx是tsc频率;

    这几行代码里面混着大数乘除法+定点浮点数,看着可能有点迷糊,但其实等价于:

    • factor = 2^64 * qpc_freq / tsc_freq

    我的机器tsc频率是3.2GHZ,按公式计算得到0xcccccccccccccc,有点怪,看到一串0xcc多少有点怀疑是不是哪搞错了,其次这个值与Win算出来的确实有点差异。
     
    实际上是正确的,运算结果只是恰好等于0xcccccccccccccc而已,然后Win并不会通过CPUID/MSR去获取CPU的标称tsc频率,而是在启动时候用一个很短的时间去动态测算CPU的真实tsc频率,算出来的频率会是3.19xxxxxxxxGHZ,而不是标称3.2G,那么最终多少会有点误差。
     
    这个运算的目的则是在CPU的tsc频率与Win的QPC频率之间算一个系数,用于频率转换,因为QPC在新一点的机器上一般都是建立在TSC基础之上(CPU要支持iTSC),在支持iTSC的情况下,QPC频率是代码写死的固定10MHZ,TSC频率却取决于CPU,tsc频率肯定不会等于10Mhz,那么必然要在中间做一层转换。至于为什么还要乘以2^64,因为如果不这么干,那就不可避免要涉及到浮点数运算有性能代价,不想用浮点数,又想保留小数点确保精度,最终就引入了定点数。VMX的tsc scaling也一样,只不过用的是2^48,微软直接用了2^64。
     
    按照2^64 * qpc_freq / tsc_freq计算出factor之后,就可以通过逆运算 __rdtsc()*factor>>64 得到以10MHZ为基准的QPC计数值。将Factor调大,自然也就可以实现倍速,本质上跟以前的修改kuser_data差不多,都是干扰参与计算的数据,最终改变计算结果,只是修改的位置不同;
     
    3. 以上MSR仅在虚拟机模式才有,但变速驱动在物理机上用的也是类似的方式,只是改了KeQueryPerfCounter所使用的另一个page中的factor,与虚拟机场景有一些差异,但基本也是一样的rdtsc*factor>>64运算逻辑,而且更简单,因为看了下我的物理机上本身时钟模式就是Period,实测物理机上似乎也并不会出现假死问题;
     
    4. 至于检测,本地检测最简单的方式就是按照公式结合tsc频率,计算出原始值然后对比,但要注意的是Windows是动态计算出的tsc频率,理论上原始factor值本身就有一定误差,所以如果仅仅只进行极低倍率的变速,有可能会误判;
     
    5. 如果直接用IDA F5去看HyperV的vcpu_run_loop函数,在某些版本的HyperV上可能看不到正确逻辑,因为紧跟VMResume和VMLanuch的一条跳转指令对IDA F5有一定干扰;
     
    6. vcpu_run_loop与vmexit_dispatch:
     
    搜索0x4402常量定位vcpu循环线程:

vmeixt_dispatch内针对各种vmexit的处理,根据switch_case定位各种exit的处理函数: 

相关文章:

对一个变速器原理的分析

背景 原本是朋友在调试一个看起来比较新的变速器驱动&#xff0c;整体来说支持两种变速模式&#xff0c;一种是进程级&#xff0c;这种用了HOOK&#xff0c;中规中矩的实现&#xff0c;原理网上都有。另一种是”系统级内核全局变速“&#xff0c;这个模式初步看了下有些特殊&a…...

秒验:可以自定义UI的一键登录服务

一键登录如今成为越来越多移动应用的首选&#xff0c;但千篇一律的登陆界面在引发用户担忧其安全性的同时&#xff0c;也容易让用户在不同APP切换时产生误解。因此&#xff0c;由国内知名移动应用开发服务商MobTech打造的一键登录工具——秒验&#xff0c;通过允许开发者自定义…...

pmm最新版本v2.40.0尝鲜体验

1 概述 PMM 是一款免费开源的企业级的数据库监控工具&#xff0c;可用来监控 MySQL、MongoDB 和 PostgreSQL 等数据库。除了指标监控&#xff0c;针对MySQL还具备SQL语句的性能监控。 官方地址是https://docs.percona.com/percona-monitoring-and-management&#xff0c;最新版…...

2023年中国数据存储市场现状及发展前景预测分析

中商情报网讯&#xff1a;当前&#xff0c;新一代信息技术快速发展推动信息产业发生了重大变革&#xff0c;数据存储行业将很快成为信息领域一个重要的产业分支。生成式人工智能催生算力需求&#xff0c;各种新兴应用场景对数据存储的容量、效率、流动性和安全性等方面提出了更…...

xlsx冻结单元格

说明 因为最近需要实现前端导出 excel 文件&#xff0c;并且对导出文件的样式进行一些修改&#xff0c;比如颜色、字体、合并单元格等&#xff0c;所以我找到了xlsx-style这个项目&#xff0c;它可以对导出的 excel 文件进行一些样式上的修改&#xff0c;这个项目是SheetJs的一…...

yolov8剪枝实践

本文使用的剪枝库是torch-pruning &#xff0c;实验了该库的三个剪枝算法GroupNormPruner、BNScalePruner和GrowingRegPruner。 安装使用 安装依赖库 pip install torch-pruning 把 https://github.com/VainF/Torch-Pruning/blob/master/examples/yolov8/yolov8_pruning.py&…...

功能基础篇6——系统接口,操作系统与解释器系统

系统 os Python标准库&#xff0c;os模块提供Python与多种操作系统交互的接口 import os import stat# 文件夹 print(os.mkdir(r./dir)) # None 新建单级空文件夹 print(os.rmdir(r./dir)) # None 删除单级空文件夹 print(os.makedirs(r.\dir\dir\dir)) # None 递归创建空…...

由于导线材质不同绕组直流电阻不平衡率超标

实测证明&#xff0c; 有的变压器绕组的直流电阻偏大&#xff0c; 有的偏差较大&#xff0c; 其主要原因是某些导线的铜和银的含量低于国家标准规定限额。 有时即使采用合格的导线&#xff0c; 但由于导线截面尺寸偏差不同&#xff0c; 也可以导致绕组直流电阻不平衡率超标。  …...

选择智慧公厕解决方案,开创智慧城市公共厕所新时代

在城市建设和发展中&#xff0c;公厕作为一个不可或缺的城市基础设施&#xff0c;直接关系到城市形象的提升和居民生活品质的改善。然而&#xff0c;传统的公厕存在着管理不便、卫生状况差、设施陈旧等问题。为了解决这些困扰着城市发展的难题&#xff0c;智慧公厕源头厂家广州…...

FFmpeg 基础模块:AVIO、AVDictionary 与 AVOption

目录 AVIO AVDictionary 与 AVOption 小结 思考 我们了解了 AVFormat 中的 API 接口的功能&#xff0c;从实际操作经验看&#xff0c;这些接口是可以满足大多数音视频的 mux 与 demux&#xff0c;或者说 remux 场景的。但是除此之外&#xff0c;在日常使用 API 开发应用的时…...

代数——第3章——向量空间

第三章 向量空间(Vector Spaces) fmmer mit den einfachsten Beispielen anfangen. (始终从最简单的例子开始。) ------------------------------David Hilbert 3.1 (R^n)的子空间 我们的向量空间的基础模型(本章主题)是n 维实向量空间 的子空间。我们将在本节讨论它。…...

2023年软考网工上半年下午真题

试题一&#xff1a; 阅读以下说明&#xff0c;回答问题1至问题4&#xff0c;将解答填入答题纸对应的解答栏内。 [说明] 某企业办公楼网络拓扑如图1-1所示。该网络中交换机Switch1-Switch 4均是二层设备&#xff0c;分布在办公楼的各层&#xff0c;上联采用干兆光纤。核心交换…...

Flutter 直接调用so动态库,或调用C/C++源文件内函数

开发环境 MacBook Pro Apple M2 Pro | macOS Sonoma 14.0 Android Studio Giraffe | 2022.3.1 Patch 1 XCode Version 15.0 Flutter 3.13.2 • channel stable Tools • Dart 3.1.0 • DevTools 2.25.0 先说下历程&#xff0c;因为我已经使用了Flutter3的版本&#xff0c;起初…...

elasticsearch(ES)分布式搜索引擎03——(RestClient查询文档,ES旅游案例实战)

目录 3.RestClient查询文档3.1.快速入门3.1.1.发起查询请求3.1.2.解析响应3.1.3.完整代码3.1.4.小结 3.2.match查询3.3.精确查询3.4.布尔查询3.5.排序、分页3.6.高亮3.6.1.高亮请求构建3.6.2.高亮结果解析 4.旅游案例4.1.酒店搜索和分页4.1.1.需求分析4.1.2.定义实体类4.1.3.定…...

198、RabbitMQ 的核心概念 及 工作机制概述; Exchange 类型 及 该类型对应的路由规则

JMS 也是一种消息机制 AMQP ( Advanced Message Queuing Protocol ) 高级消息队列协议 ★ RabbitMQ的核心概念 Connection&#xff1a; 代表客户端&#xff08;包括消息生产者和消费者&#xff09;与RabbitMQ之间的连接。 Channel&#xff1a; 连接内部的Channel。 Exch…...

系统架构设计:18 论基于DSSA的软件架构设计与应用

目录 一 特定领域软件架构DSSA 1 DSSA 2 DSSA的基本活动和产物 (1)DSSA的基本活动和产物...

Android原生实现控件outline方案(API28及以上)

Android控件的Outline效果的实现方式有很多种&#xff0c;这里介绍一下另一种使用Canvas.drawPath()方法来绘制控件轮廓Path路径的实现方案&#xff08;API28及以上&#xff09;。 实现效果&#xff1a; 属性 添加Outline相关属性&#xff0c;主要包括颜色和Stroke宽度&…...

ROS学习笔记(六)---服务通信机制

1. 服务通信是什么 在ROS中&#xff0c;服务通信机制是一种点对点的通信方式&#xff0c;用于节点之间的请求和响应。它允许一个节点&#xff08;服务请求方&#xff09;向另一个节点&#xff08;服务提供方&#xff09;发送请求&#xff0c;并等待响应。 服务通信机制在ROS中…...

常见的C/C++开源QP问题求解器

1. qpSWIFT qpSWIFT 是面向嵌入式和机器人应用的轻量级稀疏二次规划求解器。它采用带有 Mehrotra Predictor 校正步骤和 Nesterov Todd 缩放的 Primal-Dual Interioir Point 方法。 开发语言&#xff1a;C文档&#xff1a;传送门项目&#xff1a;传送门 2. OSQP OSQP&#…...

前端axios发送请求,在请求头添加参数

1.在封装接口传参时&#xff0c;定义形参&#xff0c;params是正常传参&#xff0c;name则是我想要在请求头传参 export function getCurlList (params, name) {return request({url: ********,method: get,params,name}) } 2.接口调用 const res await getCurlList(params,…...

CTF Misc(3)流量分析基础以及原理

前言 流量分析在ctf比赛中也是常见的题目&#xff0c;参赛者通常会收到一个网络数据包的数据集&#xff0c;这些数据包记录了网络通信的内容和细节。参赛者的任务是通过分析这些数据包&#xff0c;识别出有用的信息&#xff0c;例如登录凭据、加密算法、漏洞利用等等 工具安装…...

Telink泰凌微TLSR8258蓝牙开发笔记(二)

在开发过程中遇到了以下问题&#xff0c;记录一下 1.在与ios手机连接后&#xff0c;手机app使能notify&#xff0c;设备与手机通过write和notify进行数据交换&#xff0c;但是在连接传输数据一端时间后&#xff0c;设备收到write命令后不能发出notify命令&#xff0c;打印错误…...

vue3+elementPlus:el-tree复制粘贴数据功能,并且有弹窗组件

在tree控件里添加contextmenu属性表示右键点击事件。 因右键自定义菜单事件需要获取当前点击的位置&#xff0c;所以此处绑定动态样式来控制菜单实时跟踪鼠标右键点击位置。 //html <div class"box-list"><el-tree ref"treeRef" node-key"id…...

JTS:10 Crosses

这里写目录标题 版本点与线点与面线与面线与线 版本 org.locationtech.jts:jts-core:1.19.0 链接: github public class GeometryCrosses {private final GeometryFactory geometryFactory new GeometryFactory();private static final Logger LOGGER LoggerFactory.getLog…...

MySQL中的SHOW FULL PROCESSLIST命令

在MySQL数据库管理中&#xff0c;理解和监控当前正在执行的进程是至关重要的一环。MySQL提供了一系列强大的工具和命令&#xff0c;使得这项任务变得相对容易。其中&#xff0c;SHOW FULL PROCESSLIST命令就是一个非常有用的工具&#xff0c;它可以帮助我们查看MySQL服务器中的…...

VsCode 常见的配置、常用好用插件

1、自动保存&#xff1a;不用装插件&#xff0c;在VsCode中设置一下就行 2、设置ctr滚轮改变字体大小 3、设置选项卡多行展示 这样打开了很多个文件&#xff0c;就不会导致有的打开的文件被隐藏 4、实时刷新网页的插件&#xff1a;LiveServer 5、open in browser 支持快捷键…...

深度学习问答题(更新中)

1. 各个激活函数的优缺点&#xff1f; 2. 为什么ReLU常用于神经网络的激活函数&#xff1f; 在前向传播和反向传播过程中&#xff0c;ReLU相比于Sigmoid等激活函数计算量小&#xff1b;避免梯度消失问题。对于深层网络&#xff0c;Sigmoid函数反向传播时&#xff0c;很容易就…...

JavaScript 笔记: 函数

1 函数声明 2 函数表达式 2.1 函数表达式作为property的value 3 箭头函数 4 构造函数创建函数&#xff08;不推荐&#xff09; 5 function 与object 5.1 typeof 5.2 object的操作也适用于function 5.3 区别于⼀般object的⼀个核⼼特征 6 回调函数 callback 7 利用function的pr…...

2023NOIP A层联测9-天竺葵

天竺葵/无法阻挡的子序列/很有味道的题目 我们称一个长度为 k k k 的序列 c c c 是好的&#xff0c;当且仅当对任意正整数 i i i 在 [ 1 , k − 1 ] [1,k-1] [1,k−1] 中&#xff0c;满足 c i 1 > b i c i c_{i1}>b_i \times c_i ci1​>bi​ci​&#xff0c; …...

react antd table表格点击一行选中数据的方法

一、前言 antd的table&#xff0c;默认是点击左边的单选/复选按钮&#xff0c;才能选中一行数据&#xff1b; 现在想实现点击右边的部分&#xff0c;也可以触发操作选中这行数据。 可以使用onRow实现&#xff0c;样例如下。 二、代码 1.表格样式部分 //表格table样式部分{…...