C/C++逆向:switch语句逆向分析

news2024/11/12 5:57:54

在逆向分析中,switch语句会被编译器转化为不同的底层实现方式,这取决于编译器优化和具体的场景。常见的实现方式包括以下几种:

①顺序判断(if-else链):

编译器将switch语句转化为一系列的if-else语句。这种方式适用于case值较少的情况。

反汇编代码表现为一系列的比较和条件跳转指令(如CMP和JNE/JE)。
每个case的值会与目标变量进行比较,匹配时跳转到对应的代码块执行。
②跳转表(jump table):

switch语句中的case值是连续或接近连续的整数时,编译器可能会生成一个跳转表。跳转表是一个包含多个指针的数组,根据目标变量的值选择跳转到不同的代码块。

反汇编时表现为读取跳转表并跳转,例如使用JMP指令配合计算偏移。
通常会看到LEA(加载有效地址)指令和JMP指令配合使用,或者使用INDIRECT JMP,根据变量值计算偏移量并跳转。
③二分查找(binary search)

case值的范围较大且稀疏时,编译器可能会生成二分查找结构。编译器会按case值排序,并生成类似于二分查找树的结构来进行高效的判断。

反汇编时表现为多个比较和有条件的跳转,通常是递归式的分支跳转。

假设有如下C代码:

switch(x) {
    case 1:
        do_case1();
        break;
    case 2:
        do_case2();
        break;
    default:
        do_default();
        break;
}

其汇编代码可能会有以下几种表现形式:

①顺序判断:
CMP EAX, 1
JE case1
CMP EAX, 2
JE case2
JMP default
②跳转表:
MOV EAX, [EBP+var_x]  ; 获取x的值
CMP EAX, 2            ; 检查x的范围
JA default
JMP [jump_table + EAX*4]  ; 通过x的值在跳转表中选择跳转地址

jump_table中保存了各个case的代码地址。

二分查找
CMP EAX, 2
JE case2
CMP EAX, 1
JE case1
JMP default

这种实现常见于case值不连续且较大的情况。在该文中说的三种表现方式中二分查找和顺序判断这两种方式相对应容易理解,接下去的内容就蜻蜓点水大概过一下,重点会将跳转表的表现形式做说明。

逆向分析示例

假设此时有一个简单的switch代码如下:

#include <stdio.h>
#include <stdlib.h>
​
int main() {
    int nFlag = 0;
    scanf("%d", &nFlag);
    switch (nFlag)
    {
    case 10:
        printf("nFlag = 10");
        break;
    case 11:
        printf("nFlag = 11");
        break;
    case 12:
        printf("nFlag = 12");
        break;
    case 254:
        printf("nFlag = 254");
        break;
    case 255:
        printf("nFlag = 255");
        break;
    case 256:ewZ3ews
        printf("nFlag = 256");
        break;
    case 454:
        printf("nFlag = 454");
        break;
    case 455:
        printf("nFlag = 455");
        break;
    case 456:
        printf("nFlag = 456");
        break;
    default:
        break;
    }
    system("pause");
    return 0;
}

此时使用Visual Studio对该代码进行编译,生成exe文件,对应的编译配置为Debug-x86;

Debug-x86程序分析
静态分析:

将生成的程序载入IDA中进行静态分析

Function Window中定位到main函数:

(关于定位main函数的各种方法请查看前面的文章),接下去开始逐步对进行代码分析:

mov     [ebp+var_C], 0
lea     eax, [ebp+var_C]
push    eax
push    offset Format   ; "%d"
call    j__scanf
add     esp, 8

上述这个汇编代码片段的作用是调用scanf函数从用户那里读取一个整数,并将其存储在局部变量中。我们逐步分析这个代码:

mov [ebp+var_C], 0:这条指令将值 0 存储到局部变量 [ebp+var_C] 中,通常用于初始化变量 var_C,确保它在使用前被清零。[ebp+var_C]x86汇编中的一种内存寻址方式,用于访问栈上的局部变量。为了理解它的含义,需要了解栈帧(stack frame)和ebp寄存器在函数调用中的作用。

①当函数被调用时,系统会为这个函数在栈上分配一段内存,称为栈帧。栈帧中存储着局部变量、函数参数以及保存的寄存器值等信息。
②ebp(Base Pointer,基指针)寄存器用于标记栈帧的基地址,即函数栈帧的一个固定参考点。
③在函数调用期间,ebp 通常不会改变,函数中的局部变量和参数都可以相对于 ebp 寄存器通过偏移量进行访问。

ebp+var_C 实际上表示函数栈帧中的一个局部变量的内存地址。在函数执行时,ebp 指向栈帧的底部,局部变量通常存储在 ebp 的下方,var_C 是这个局部变量的偏移量。[ebp+var_C]:表示访问存储在ebp 基地址加上 var_C 偏移量处的内存位置的值。接着我们继续解释代码:

lea eax, [ebp+var_C]:这条指令将局部变量 var_C 的地址加载到寄存器 eax 中。lea 指令用于获取某个变量的地址,而不是变量的值,因此 eax 现在保存的是变量 var_C 的地址。

push eax:将 eax(也就是变量 var_C 的地址)压入栈中。这是为了给后续的 scanf 函数准备参数,scanf 需要知道存储输入结果的变量地址。

push offset Format:将格式字符串的地址(这里是 "%d",表示整数格式)压入栈中。这是 scanf 函数的第二个参数,用来指定输入的数据类型。

call j__scanf:调用 scanf 函数,它会根据传入的格式字符串和变量地址,从用户那里读取输入并将值存储在 var_C 中。

add esp, 8:由于之前在栈中压入了两个 4 字节的参数(格式字符串的地址和变量的地址),scanf 调用后需要通过这条指令恢复栈平衡,将栈指针 esp 增加 8,清理掉这两个参数。

这个汇编代码大致等价于以下 C 代码:

int var_C = 0;
scanf("%d", &var_C);

scanf函数调用完毕,后续代码如下:

mov     eax, [ebp+var_C]
mov     [ebp+var_D4], eax
cmp     [ebp+var_D4], 0FFh
jg      short loc_4119C2
cmp     [ebp+var_D4], 0FFh
jz      loc_411A34
mov     ecx, [ebp+var_D4]
sub     ecx, 0Ah        ; switch 245 cases
mov     [ebp+var_D4], ecx
cmp     [ebp+var_D4], 0F4h
ja      def_4119BB      ; jumptable 004119BB default case, cases 13-253
                        ; jumptable 004119F1 default case, cases 257-453
mov     edx, [ebp+var_D4]
movzx   eax, ds:byte_411AF8[edx]
jmp     ds:jpt_4119BB[eax*4] ; switch jump  

这段汇编代码的主要作用是对局部变量 var_D4 进行一系列的操作和比较,并根据比较结果跳转到不同的代码位置。我们逐步解释代码的含义:

mov eax, [ebp+var_C]:将位于 [ebp+var_C] 的值(即局部变量 var_C 的值)加载到 eax 寄存器中。

mov [ebp+var_D4], eax:将 eax 中的值(即用户输入的 var_C 的值)存储到 [ebp+var_D4],即局部变量 var_D4 中。

cmp [ebp+var_D4], 0FFh:将 var_D4 的值与 0xFF(255 十进制)进行比较。

jg short loc_4119C2:如果比较结果为 "大于"(jg,Jump if Greater),则跳转到 loc_4119C2代码如下:

loc_4119C2:                             ; CODE XREF: _main+5D↑j
                 mov     ecx, [ebp+var_D4]     
                 sub     ecx, 100h       ; switch 201 cases
                 mov     [ebp+var_D4], ecx
                 cmp     [ebp+var_D4], 0C8h
                 ja      def_4119BB      ; jumptable 004119BB default case, cases 13-253
                                         ; jumptable 004119F1 default case, cases 257-453
                 mov     edx, [ebp+var_D4]
                 movzx   eax, ds:byte_411C04[edx]
                 jmp     ds:jpt_4119F1[eax*4] ; switch jump

cmp [ebp+var_D4], 0FFh:再次比较 var_D4 的值与 0xFF,这与前面相同,继续进行比较。

jz loc_411A34:如果比较结果为 "相等"(jz,Jump if Zero),则跳转到 loc_411A34,代码如下。

loc_411A34:                             ; CODE XREF: _main+69↑j
                push    offset aNflag255 ; "nFlag = 255"
                call    j__printf
                add     esp, 4
                jmp     short def_4119BB ; jumptable 004119BB default case, cases 13-253
                                        ; jumptable 004119F1 default case, cases 257-453

mov ecx, [ebp+var_D4]:将局部变量 var_D4 的值加载到 ecx 寄存器中。

sub ecx, 0Ah:将 ecx 中的值减去 0xA(十进制 10)。那么在这边为什么要减去10呢?其实在反汇编代码中,var_D4 的值被减去 0xA(10),这是与 switch-case 语句的基准值对齐的一部分优化。为了更清楚地解释这个过程,我们需要理解以下几个概念:

    在高级语言中,switch-case 语句通常用于根据不同的值跳转到不同的代码分支。当代码被编译时,编译器会尝试优化 switch-case 的实现,特别是当 case 的值范围较密集时,编译器可能会选择使用 跳转表(jump table) 来提高效率。
    跳转表允许程序通过索引快速跳转到对应的分支,而不是通过一系列的 if-else 或 cmp 指令逐个比较所有 case 值。跳转表的索引通常是 case 值的偏移量,因此编译器会对 switch-case 的 case 值进行某种形式的调整,使得 case 的最小值成为跳转表的起始索引。

在此处的反汇编代码中,var_D4 被减去 0xA(10),这很可能是因为 switch-case 中的 case 值范围不是从 0 开始的;很有可能此时 switch-case 语句中 case 的最小值是 10,那么减去 0xA 就将输入值与 case 的最小值对齐,使其从 0 开始索引跳转表。接着回到汇编代码:

mov [ebp+var_D4], ecx:将减法操作后的结果(ecx 中的值)存储回局部变量 var_D4

cmp [ebp+var_D4], 0F4h:比较 var_D4 的新值和 0xF4(十进制 244)。

ja def_4119BBja 是无符号大于跳转指令(Jump if Above),即如果 var_D4 的值大于 0xF4,则跳转到 def_4119BB 处执行。(这个事实上就是默认分支比较简单有兴趣可以看一下,我们的重点在下面的跳转表)

def_4119BB:                             ; CODE XREF: _main+88↑j
                                         ; _main+9B↑j ...
                 mov     esi, esp        ; jumptable 004119BB default case, cases 13-253
                                         ; jumptable 004119F1 default case, cases 257-453
                 push    offset Command  ; "pause"
                 call    ds:__imp__system
                 add     esp, 4
                 cmp     esi, esp
                 call    j___RTC_CheckEsp
                 xor     eax, eax
                 push    edx
                 mov     ecx, ebp
                 push    eax
                 lea     edx, dword_411AC8
                 call    j_@_RTC_CheckStackVars@8 ; _RTC_CheckStackVars(x,x)
                 pop     eax
                 pop     edx
                 pop     edi
                 pop     esi
                 pop     ebx
                 mov     ecx, [ebp+var_4]
                 xor     ecx, ebp
                 call    j_@__security_check_cookie@4 ; __security_check_cookie(x)
                 add     esp, 0D4h
                 cmp     ebp, esp
                 call    j___RTC_CheckEsp
                 mov     esp, ebp
                 pop     ebp
                 retn
 _main           endp

mov edx, [ebp+var_D4]:将局部变量 var_D4 的值加载到寄存器 edx 中。

!!movzx eax, ds:byte_411AF8[edx]:这条指令从地址 ds:byte_411AF8[edx] 处取出一个字节,并将其零扩展到 eax 寄存器中。

①movzx 是 "移动并零扩展"(move with zero extension),它从内存中读取一个字节并将其扩展为 32 位存储在 eax 中,确保高位被清零。
②ds:byte_411AF8[edx] 表示从数据段 ds 的偏移地址 0x411AF8 开始,通过 edx 的值作为偏移量来访问一个字节数据。
ds:byte_411AF8[edx]解释:

这个部分指的是一个基于段寄存器和偏移量的内存访问。让我们逐步解析它:

ds:这是数据段寄存器(Data Segment)。

byte_411AF8:这是一个内存地址,表示一个位于 411AF8地址处的字节变量。此时411AF8地址存放的数据如下:

byte_411AF8     db 0, 1, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4
                                         ; DATA XREF: _main+94↑r
                 db 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 ; indirect table for switch statement
                 db 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4
                 db 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4
                 db 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4
                 db 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4
                 db 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4
                 db 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4
                 db 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4
                 db 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4
                 db 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4
                 db 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4
                 db 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4
                 db 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3

这是一个查找表用于处理 switch-case 语句。表中大多数值是 4,这可能是查找表的默认值,用于表示跳转到 switch-case 的默认分支(default case)。特定位置上,例如第一个位置是 0,第二个位置是 1,第三个位置是 2,最后一个位置是 3,这些值对应于 switch-case 语句中的有效分支。

[edx]:表示用 edx 中的值作为索引来访问该数组中的具体字节。

到此我们就可以清楚的知道ds:byte_411AF8[edx]表达的含义:表示对 byte_411AF8 数组的访问,其中 edx 寄存器中的值用作数组的索引。在获取数组的对应索引后,执行下一条指令:

!!jmp ds:jpt_4119BB[eax*4]

这条指令是一个基于跳转表的间接跳转,结合跳转表的内容来看,它是通过 eax 中的值从跳转表中选择一个跳转目标,然后执行相应的代码分支(case)。

以下为跳转表jpt_4119BB的内容,在跳转表中存储了各个 switch-case 语句的跳转地址。每一行表示一个 dd 数据(dd 表示定义双字节,4 个字节),即一个内存地址。正因为跳转表中存储的case语句的跳转地址为dd类型,所以在索引需要乘以 4 来计算跳转表的偏移量。

jpt_4119BB     dd offset loc_4119F8    ; DATA XREF: _main+9B↑r
               dd offset loc_411A07    ; jump table for switch statement
               dd offset loc_411A16
               dd offset loc_411A25
               dd offset def_4119BB

jmp ds:jpt_4119BB[eax*4]指令通过 eax 的值(在上条指令的查询表中获取)计算出的偏移量,跳转到跳转表中指定的地址。

例如,此时程序接收到的用户输入为254,此时我们根据代码做一个解析(解析放在注释~~~部分):

mov     eax, [ebp+var_C]                          ~~~程序接收到254~~~
mov     [ebp+var_D4], eax
cmp     [ebp+var_D4], 0FFh
jg      short loc_4119C2
cmp     [ebp+var_D4], 0FFh
jz      loc_411A34
mov     ecx, [ebp+var_D4]
sub     ecx, 0Ah        ; switch 245 cases        ~~~254-10 = 244~~~
mov     [ebp+var_D4], ecx
cmp     [ebp+var_D4], 0F4h
ja      def_4119BB      ; jumptable 004119BB default case, cases 13-253
                        ; jumptable 004119F1 default case, cases 257-453
mov     edx, [ebp+var_D4]               ~~~244 => edx~~~~
movzx   eax, ds:byte_411AF8[edx]        ~~~以edx(244)为索引在查询表byte_411AF8中查询~~~
jmp     ds:jpt_4119BB[eax*4] ; switch jump          ~~~根据查询到的值在跳转表中做跳转~~~

这个时候我们来说以下程序以edx(244)为索引在查询表byte_411AF8中查询的结果:

查询到的结果为3;接着执行jmp ds:jpt_4119BB[eax*4]进行跳转(间接跳转),此时的跳转指令为jmp ds:jpt_4119BB[12],对照跳转表:

我们最后会跳转进入loc_411A25分支,此时该分支代码如下:

loc_411A25:                             ; CODE XREF: _main+9B↑j
                                        ; DATA XREF: .text:jpt_4119BB↓o
                push    offset aNflag254 ; jumptable 004119BB case 254
                call    j__printf
                add     esp, 4
                jmp     short def_4119BB ; jumptable 004119BB default case, cases 13-253
                                        ; jumptable 004119F1 default case, cases 257-453

成功定位跳转至case 254分支并执行代码;其他根据查询表、跳转表进行跳转的分支跳转也是按照这个流程,这边就不再做其他赘述了。

动态分析

x86-Debug程序载入x86dbg中,进行动态分析:

右击汇编代码窗口,选择搜索->所有模块->字符串输入特征字符串(nFlag)定位到main函数,并开始分析:

此时持续按F8进行步过运行程序,直到运行到如下函数后就无法继续运行:

此时打开程序窗口可以看到此时程序正在等待用户输入,这个时候基本上可以断定switchre_x86-debug.4D10A0函数为scanf函数;存放用户输入的变量地址存放在eax寄存器中,此时输入254为例子查看程序的具体执行。

回车后程序继续执行;在接收到用户输入的数据后先与0xFF(255)进行比较:若大于255则跳转至switchre_x86-debug.4D19C2位置,若等于0xFF则跳转至switchre_x86-debug.4D1A34位置。(这些都比较简单,如果感觉看着吃力的话可以再去看看笔者之前的文章这边我们的重点还是放在Switch分支结构的跳转表表现形式)

因为此时我们输入的值为254所以既不大于也不等于0xFF(x64dbg可以在程序运行过程中修改内存中变量的值),接着运行以下指令。

将用输入的值传入ecx寄存器,接着减去A(10);接着与0xF4(244)进行比较,大于244则转入switchre_x86-debug.4D1A7D(默认分支),若不大于则将被减去10的用户输入数据转入edx;此时我们可以在寄存器窗口查看当前寄存器中的值。

接着进行地址索引(间接跳转),索引得到的值:0xF4+4D1AF8 = 4D1BEC此时我们地址索引的代码如下:

这个时候我们可以直接在内存窗口,输入ctrl + G进行值的查看;

可以看到此时内存中的值为03,其实从代码中我们也可以发现,程序会将地址索引到的值放入eax寄存器中:此时也可以查看寄存器窗口eax寄存器中的值为3:

接着查看下面的代码:最后执行jmp跳转指令,此时会进行地址索引(其实就是间接跳转),获取最后case的执行地址。

这个时候我们接着计算地址:(eax)3*4 + 4D1AE4 = 4D 1AF0(该地址是存储case代码地址的内存地址),我们接着在内存窗口中查看地址为4D 1AF0的值,因为是小端序存储,所以得到的值应该是:004D1A25。

这个地址就是case对应的地址位置,我们在汇编窗口进行定位,可以看到此时已经跳转至case 254分支。

其他的分支也是如此,此处就不做过多说明。

在本篇文章中,我们深入探讨了 switch-case 语句在汇编代码中的表现形式,特别是如何通过跳转表来优化分支跳转逻辑。通过分析具体的反汇编代码,我们能够清晰地理解编译器如何通过查找表和间接跳转提高执行效率。

间接跳转是一种程序控制流机制,它通过一个变量(通常是寄存器或内存地址)中的值来决定程序的跳转目标,而不是直接跳转到一个明确指定的地址。与直接跳转不同,间接跳转的目标地址是在程序运行时动态确定的。

这不仅帮助我们更好地理解反汇编代码中的结构,也为我们逆向分析复杂程序提供了重要的工具和思路。希望通过这些案例,你能够对 switch-case 语句的反汇编分析有更加深入的理解。此外x64-release程序的逆向过程也与x86-debug相似,只不过release代码中有对程序进行优化,有兴趣也可以动手做做。

另外,请多多关注笔者的VX-公ZH-【风铃Sec】,你们的支持就是我更新的动力!

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

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

相关文章

【第十四章:Sentosa_DSML社区版-机器学习时间序列】

目录 【第十四章&#xff1a;Sentosa_DSML社区版-机器学习时间序列】 14.1 ARIMAX 14.2 ARIMA 14.3 HoltWinters 14.4 一次指数平滑预测 14.5 二次指数平滑预测 【第十四章&#xff1a;Sentosa_DSML社区版-机器学习时间序列】 14.1 ARIMAX 1.算子介绍 考虑其他序列对一…

Flutter鸿蒙化(windows)

Flutter鸿蒙化&#xff08;windows&#xff09; 参考资料Window配置Flutter的鸿蒙化环境下载配置环境变量HarmonyOS的环境变量配置配置Flutter的环境变量Flutter doctor -v 检测的问题flutter_flutter仓库地址的警告问题Fliutter doctor –v 报错[!] Android Studio (version 2…

计算机前沿技术-人工智能算法-大语言模型-最新论文阅读-2024-09-18

计算机前沿技术-人工智能算法-大语言模型-最新论文阅读-2024-09-18 1. The Application of Large Language Models in Primary Healthcare Services and the Challenges W YAN, J HU, H ZENG, M LIU, W LIANG - Chinese General Practice, 2024 人工智能大语言模型在基层医疗…

软媒市场新探索:软文媒体自助发布,开启自助发稿新篇章

在繁华喧嚣的软媒市场中,每一个声音都在竭力呼喊,每一个品牌都在奋力展现。而软文,作为一种温柔而坚韧的营销力量,正逐渐崭露头角。特别是软文媒体自助发布平台的出现,更是为企业提供了一个全新的、高效的自助发稿渠道。 软媒市场自助发布平台,正如其名,是一个让企业能够自主发…

离职员工客户如何管理?解锁2024企业微信新功能

公司里员工来来去去很正常&#xff0c;但每次有人走&#xff0c;老板们都会头疼&#xff0c;因为客户信息得有人接着管。客户对公司来说太重要了&#xff0c;不能丢。2024年&#xff0c;企业微信出了个新招&#xff0c;就是员工离职后&#xff0c;客户信息可以轻松转给新来的员…

JVM的基本概念

目录 一、JVM的内存划分 二、JVM的类加载过程 三、JVM的垃圾回收机制&#xff08;GC&#xff09; 四、分代回收 一、JVM的内存划分 一个运行起来的Java进程&#xff0c;就是一个Java虚拟机&#xff0c;就需要从操作系统中申请一大块内存。申请的内存会划分为不同的区域&…

Maven笔记(一):基础使用【记录】

Maven笔记&#xff08;一&#xff09;-基础使用 Maven是专门用于管理和构建Java项目的工具&#xff0c;它的主要功能有&#xff1a; 提供了一套标准化的项目结构 Maven提供了一套标准化的项目结构&#xff0c;所有IDE(eclipse、myeclipse、IntelliJ IDEA 等 项目开发工具) 使…

计算机前沿技术-人工智能算法-大语言模型-最新论文阅读-2024-09-17

计算机前沿技术-人工智能算法-大语言模型-最新论文阅读-2024-09-17 1. Large Language Models in Biomedical and Health Informatics: A Review with Bibliometric Analysis H Yu, L Fan, L Li, J Zhou, Z Ma, L Xian, W Hua, S He… - Journal of Healthcare …, 2024 生物…

HarmonyOS应用开发(组件库)--组件模块化开发、工具包、设计模式(持续更新)

致力于&#xff0c;UI开发拿来即用&#xff0c;提高开发效率 正则表达式...手机号校验...邮箱校验 文件判断文件是否存在 网络下载下载图片从沙箱中图片转为Base64格式从资源文件中读取图片转Base64 组件输入框...矩形输入框...输入框堆叠效果&#xff08;用于登录使用&#xf…

【自动驾驶】决策规划算法(二)参考线模块Ⅰ| 平滑算法与二次规划

写在前面&#xff1a; &#x1f31f; 欢迎光临 清流君 的博客小天地&#xff0c;这里是我分享技术与心得的温馨角落。&#x1f4dd; 个人主页&#xff1a;清流君_CSDN博客&#xff0c;期待与您一同探索 移动机器人 领域的无限可能。 &#x1f50d; 本文系 清流君 原创之作&…

(学习记录)使用 STM32CubeMX——GPIO引脚输入配置

STM32F103C8T6的GPIO引脚输入配置 时钟配置 &#xff08;学习记录&#xff09;使用 STM32CubeMX——配置时钟&#xff08;入门&#xff09;https://blog.csdn.net/Wang2869902214/article/details/142423522 GPIO 引脚输出配置 &#xff08;学习记录&#xff09;使用 STM32…

Springcloud框架-能源管理系统-能源管理系统源码-能源在线监测平台-双碳平台

一、介绍 基于SpringCloud的能管管理系统-能源管理平台源码-能源在线监测平台-双碳平台源码-SpringCloud全家桶-能管管理系统源码 有需者咨询&#xff0c;非诚勿扰&#xff1b; 二、软件架构 二、功能介绍 三、数字大屏展示 四、数据采集原理 五、软件截图

macos pyenv 安装python tk 、tkinter图形库方法步骤和使用总结

在macos中&#xff0c; pyenv 是一款用来管理多版本python 的工具&#xff0c; 我们常用的tk图形库是一个独立的工具库&#xff0c;我们在python里面使用的tkinter模块仅是调用这个独立的tk图形库&#xff0c; 所以如果我们希望在python里面使用它&#xff0c; 就必须要先安装t…

委托的注册及注销+观察者模式

事件 委托变量如果公开出去&#xff0c;很不安全&#xff0c;外部可以随意调用 所以取消public,封闭它&#xff0c;我们可以自己书写两个方法&#xff0c;供外部注册与注销&#xff0c;委托调用在子方法里调用&#xff0c;这样封装委托变量可以使它更安全&#xff0c;这个就叫…

金融加密机的定义与功能

金融加密机是一种用于保护金融交易数据和信息安全的重要安全设备。它通过硬件和软件的多重保障&#xff0c;确保金融交易中的敏感数据不被泄露或篡改。以下是关于金融加密机的详细介绍&#xff1a; 一、定义与功能 金融加密机是一种硬件安全设备&#xff0c;通过实现各种密码算…

深度deepin初体验(一)系统详细安装过程 | 国产系统

这里写自定义目录标题 深度deepin初体验&#xff08;一&#xff09;系统详细安装过程1.介绍2.安装要求3.环境4.创建虚拟机/系统升级系统选择语言硬盘分区备份文件拷贝系统重启常规设置 深度deepin初体验&#xff08;一&#xff09;系统详细安装过程 1.介绍 深度deepin是在debi…

Python开发深度学习常见安装包 error 解决

Python Python 是一种广泛使用的高级编程语言&#xff0c;它以其清晰的语法和代码可读性而闻名。Python 支持多种编程范式&#xff0c;包括面向对象、命令式、函数式和过程式编程。由于其简洁性和强大的标准库&#xff0c;Python 成为了数据科学、机器学习、网络开发、自动化脚…

气膜馆:新型场馆的盈利之道—轻空间

气膜馆作为一种创新的场馆形式&#xff0c;凭借其先进的技术和灵活的应用&#xff0c;正在快速崛起&#xff0c;展现出广阔的市场前景与丰富的盈利潜力。通过多元化的经营模式&#xff0c;气膜馆为创业者提供了前所未有的商机。本文将深入分析气膜馆的盈利模式及其在市场中的竞…

气膜储煤棚:未来能源管理的新选择—轻空间

在全球对可持续发展与环保的日益重视下&#xff0c;传统的煤炭储存方式面临着诸多挑战。气膜储煤棚应运而生&#xff0c;成为现代煤炭储存的理想解决方案。本文将深入探讨气膜储煤棚的优势与应用&#xff0c;为企业提供新的思路。 先进的技术设计 气膜储煤棚采用创新的气膜技术…

AcWing算法基础课-790数的三次方根-Java题解

大家好&#xff0c;我是何未来&#xff0c;本篇文章给大家讲解《AcWing算法基础课》790 题——数的三次方根。本题考查算法为浮点数二分查找。本文详细介绍了一个使用二分法计算浮点数三次方根的算法。通过逐步逼近目标值&#xff0c;程序能够在给定的区间内精确计算出结果&…