函数栈帧详解

news2024/9/28 7:21:03

写在前面

这个模块临近C语言的边界,学起来需要一定的时间,不过当我们知道这些知识后,在C语言函数这块我们看到的不仅仅是表象了,可以真正了解函数是怎么调用的。不过我的能力有限,下面的的知识若是不当,还请各位斧正。

知识点储备

  • 初步了解函数( 这里的所说的函数我们默认为自定义函数)
  • 了解C程序地址空间
  • 基本的寄存器
  • 知道一些汇编语言

函数的概念

函数大家应该都很熟悉了,这里就不细说了。我们看看就行

ret_type fun_name(para1, * )
{
    statement;  //语句项
}

ret_type  返回类型
fun_name  函数名
para1     函数参数  

虚拟地址空间

我们一直说 :“全局变量的生命周期是所在的整个程序”、“static修饰的变量的生命周期变长了”、以及“最重要的临时变量出函数就要被销毁”。不过我们要知道这是因为什么.在C语言中我们所创建的每一个变量都会有自己空间的的存储类别,就比如汽车一般不会停在高楼那样,每一个事物都会有自己的集合,计算机的数据存储也是如此,我们这看一下.

在计算机中,我们把内存分为若干的区间(这里暂时这样理解),每一个区间保存特定的数据,我们先来看C语言程序地址空间,也是虚拟地址空间.

image-20230301103430508

上面不做解析,我们在后面会学到,大家把这个图给记住就可以了.看一下代码,来验证一下.可以看出局部变量存储在栈上且栈空间是沿着向低地址方向开辟的,堆区与之相反.

#include<stdio.h>                                                                             
#include<stdlib.h>

int g_val1 = 10;
int g_val2 = 10;
int g_val3;
int g_val4;

int main()
{
    const char* str = "abcdef";

    printf("code: %p\n", main);

    printf("read only : %p\n", str);

    printf("init g_val1 : %p\n", &g_val1);
    printf("init g_val2 : %p\n", &g_val2);
    printf("uninit g_val2 : %p\n", &g_val3);
    printf("uninit g_val2 : %p\n", &g_val4);

    char* p1 = (char*)malloc(sizeof(char*) * 10);
    char* p2 = (char*)malloc(sizeof(char*) * 10);

    printf("heap addr : %p\n", p1);
    printf("heap addr : %p\n", p2);

    printf("stack addr : %p\n", &str);
    printf("stack addr : %p\n", &p1);
    printf("stack addr : %p\n", &p2);

    return 0;
}

image-20230301103628715

寄存器

函数的调用与CPU中的寄存器有很大关系,下面有一些基本知识

  • eax:通用寄存器,保留临时数据,常用于返回值
  • ebx:通用寄存器,保留临时数据
  • ebp:栈底寄存器
  • esp:栈顶寄存器
  • eip:指令寄存器,保存当前指令的下一条指令的地址,衡量走到了那一步

简单汇编语言

这里是一些常见的汇编语言的指令,这里先和大家列出,后面我们产看汇编语言的时候,直接回来看这些语言是什么意思.

  • mov:数据转移指令
  • push:数据入栈,同时esp栈顶寄存器也要发生改变
  • pop:数据弹出至指定位置,同时esp栈顶寄存器也要发生改变
  • sub:减法命令
  • add:加法命令
  • call:函数调用,1. 压入返回地址 2. 转入目标函数
  • jump:通过修改eip,转入目标函数,进行调用
  • ret:恢复返回地址,压入eip,类似pop eip命令

函数栈帧

看了这么多知识,我们一定会感到很是枯燥,觉得这和函数栈帧一点关系都没有,不要着急,下面就开始我们正式的内容。这里为了便于理解,我们这么看栈的空间,我们就多画些图片

我们知道 main函数也是一个函数,它也是能够被调用,所以main函数也会形成栈帧。

image-20230301105834840

样例代码

下面我们用一个很简单的代码来和大家简单的谈函数的原理,代码虽小,但是却能反应问题.

int MyAdd(int a, int b)
{
	int c = a + b;
	return c;
}

int main()
{
	int x = 0xA;
	int y = 0xB;
	int z = 0;

	z = MyAdd(x, y);
	printf("z = %d\n", z);
	return 0;
}

我们直接开始调试,然后转到反汇编,打开寄存器.
在这里插入图片描述

汇编代码

我将汇编代码复制下来,我们一步一步分析这些东西.

int main()
{
00821E40  push        ebp  
00821E41  mov         ebp,esp  
00821E43  sub         esp,0E4h  
00821E49  push        ebx  
00821E4A  push        esi  
00821E4B  push        edi  
00821E4C  lea         edi,[ebp-24h]  
00821E4F  mov         ecx,9  
00821E54  mov         eax,0CCCCCCCCh  
00821E59  rep stos    dword ptr es:[edi]  
00821E5B  mov         ecx,82C003h  
00821E60  call        0082130C  
	int x = 0xA;
00821E65  mov         dword ptr [ebp-8],0Ah  
	int y = 0xB;
00821E6C  mov         dword ptr [ebp-14h],0Bh  
	int z = 0;
00821E73  mov         dword ptr [ebp-20h],0  

	z = MyAdd(x, y);
00821E7A  mov         eax,dword ptr [ebp-14h]  
00821E7D  push        eax  
00821E7E  mov         ecx,dword ptr [ebp-8]  
00821E81  push        ecx  
00821E82  call        008211E5  
00821E87  add         esp,8  
00821E8A  mov         dword ptr [ebp-20h],eax  
	printf("z = %d\n", z);
00821E8D  mov         eax,dword ptr [ebp-20h]  
00821E90  push        eax  
00821E91  push        827BCCh  
00821E96  call        008213A2  
00821E9B  add         esp,8  
	return 0;
00821E9E  xor         eax,eax  
}
00821EA0  pop         edi  
00821EA1  pop         esi  
00821EA2  pop         ebx  
00821EA3  add         esp,0E4h  
00821EA9  cmp         ebp,esp  
00821EAB  call        00821235  
00821EB0  mov         esp,ebp  
00821EB2  pop         ebp  
00821EB3  ret  

这里把寄存器再次简单的说一下.

  • ebp指向栈底
  • esp指向栈顶
  • eip指向下一个即将执行的地址 还未执行

main函数栈帧形成

注意,main函数也是有栈帧的,这里我们默认mian函数栈帧已经创建完成了,只看MyAdd函数栈帧的形成,他们都是一样的,我们希望以最小的成本来和大家经行分析.

代码逻辑

下面开始main函数内部大代码逻辑,这是局部变量空间的开辟.

int x = 0xA;
01011E65  mov         dword ptr [ebp-8],0Ah 
                      //在ebp-8处在开辟一个空间,将x的值放进去

image-20230301105806031

	int y = 0xB;
    01011E6C  mov      dword ptr [ebp-14h],0Bh  
                       //在ebp-14处在开辟一个空间,将y的值放进去

image-20230301110453899

		int z = 0;
        00821E73  mov   dword ptr [ebp-20h],0  
                       //在ebp-20处在开辟一个空间,将z的值放进去 

image-20230301110638966

我们可以通过汇编语言看出可以看出,x、y、z 的空间是不连续的 ,这是VS保护机制, 防止一些程序员猜测对应的地址,不过一些老的编译器是不会这么做的,这里不要求我们记忆.

形参拷贝

下面汇编语言继续往下面走,我们此时遇到了函数的调用,那么编译器首先要做的就是形参的拷贝.

这就话的意思是把ebp-14(也就是y) 赋值给eax eax是一个临时的寄存器,保留临时数据,常用于返回值,这里的寄存器都是一个临时的容器,方便

	z = MyAdd(x, y);
00821E7A  mov         eax,dword ptr [ebp-14h]  
在这里插入图片描述

此时编译器会做下面动作.把eax里面的数据推送到栈中.push命令将eax的值放入栈中,同时栈顶esp的位置发生变化,变化的大小是4个字节,因为y是int型

00821E7D  push        eax  

image-20230301111820019

此时我们更新一下我们地址空间的总图,把栈顶寄存器的值保存一下.

image-20230301112017071

下面同理,我们也罢x的值进行拷贝一番,把ebp-8(也就是x) 赋值给ecx,然后推送到栈帧中.

    00821E7E  mov         ecx,dword ptr [ebp-8]  
    00821E81  push        ecx  
在这里插入图片描述

我们还是要更新一下栈顶的位置发生变化.
image-20230301112249321

下面我们开始总结一下上面的过程.

  • 编译器首先为main函数开辟栈帧,然后按照顺序进行变量空间的开辟和初始化
  • 遇到调用函数时,临时变量的形成(实参的临时拷贝)在函数调前就完成了
  • 形参实例化的顺序是从右向左依次形成的,这个有很大的用处,只不过在C++中会体现
  • 形参的空间是紧邻的,毕竟形参的变量空间的创建伴随着栈顶的变化.

函数调用

下面我们正式开始函数的调用工作,此时涉及很多的东西.这里先说一下call命令的作用

  • 压入返回地址 (最重要的)
  • 转入目标函数

我们说压入返回地址,此时谁是返回地址呢call命令的下一条命令的地址. 为什么要压入?根本原因是函数调用完毕,我们要放回mian函数执行其他的代码,需要返回就需要一个回来的坐标.

00821E82  call        008211E5

在这里插入图片描述

此时我们一跳这个指令,那么此时编译器会做两个事情,把call指令的地址压入栈顶,顺便修改esp,让后进入call 的地址处.

在这里插入图片描述

下面开始jump指令,这是一个跳转指令,把寄存器eip的数据从当前指令的地址改变成jump的地址,我们把这个目的地址认为是我们调用函数的地址,此时转入目标函数.

image-20230301115914240

这里存在一个问题, esp 也就是栈顶的指针,为何发生变化了?这是因为call指令的第一个作用,至于改变了多少,我们先不关心,只需要知道上升的空间里面是保存一个指针的,那么就是4个或者8个字节.

image-20230301192710989

MyAdd函数栈帧的形成

我们继续开始下面最关键的一个步骤,由于我的不小心,把代码从调试状态退出来了,此时这重新进入调试,那么汇编语言中的相关地址会发生变化,不过代码的逻辑是不会发生变化的,我们还是把汇编语言进行复制出来,这里只看MyAdd函数的汇编语言.

int MyAdd(int a, int b)
{
001E2EC0  push        ebp  
001E2EC1  mov         ebp,esp  
001E2EC3  sub         esp,0CCh  
001E2EC9  push        ebx  
001E2ECA  push        esi  
001E2ECB  push        edi  
001E2ECC  lea         edi,[ebp-0Ch]  
001E2ECF  mov         ecx,3  
001E2ED4  mov         eax,0CCCCCCCCh  
001E2ED9  rep stos    dword ptr es:[edi]  
001E2EDB  mov         ecx,1EC003h  
001E2EE0  call        001E130C  
	int c = 0;
001E2EE5  mov         dword ptr [ebp-8],0  
	c =a + b;
001E2EEC  mov         eax,dword ptr [ebp+8]  
001E2EEF  add         eax,dword ptr [ebp+0Ch]  
001E2EF2  mov         dword ptr [ebp-8],eax  
	return c;
001E2EF5  mov         eax,dword ptr [ebp-8]  
}
001E2EF8  pop         edi  
001E2EF9  pop         esi  
001E2EFA  pop         ebx  
001E2EFB  add         esp,0CCh  
001E2F01  cmp         ebp,esp  
001E2F03  call        001E1235  
001E2F08  mov         esp,ebp  
001E2F0A  pop         ebp  
001E2F0B  ret 

压入栈底

此时编译器首先要做的就是把原本栈底寄存器ebp中的数据压入到栈中,同时栈顶也发生变化

00821740  push        ebp  

这条命令是将ebp(也就是栈底)的内容压入栈中,同时栈顶也发生变化
image-20230301193616424

修改栈底

下面开始数据转移指令,该命令的意思是将esp的内容覆盖到ebp中,也就是此时栈底变得和栈顶一样了,该过程没有通过内存,直接通过CPU. 那么我们可能会发出疑惑,那main函数栈底怎么办,是不是找不回来了?实际上不是的,上一步我们不是把栈底的内容给保存了吗!

00821741  mov         ebp,esp 

image-20230301194044842

修改栈顶

下面我们开始计算新的栈顶就是在哪里了,根据sub指令,可以计算出新的栈顶在哪里.该命令的意思是esp减去一定的值,结果放在esp中到这里我们已经简单的形成了MyAdd函数的栈帧了

00821743  sub         esp,0CCh
                      //0CCh 的大小和你定义的函数的规模有关

image-20230301194506718

代码逻辑

下面我们开始函数内部的代码逻辑,都是很简单的.这和main的变量开辟一样

int c = 0;
001E2EE5  mov         dword ptr [ebp-8],0  
                      //在ebp-8处在开辟一个空间,将c的值放进去

image-20230301194817461

下面我们开始一步步分析后面的逻辑,注意看,我们上面形参的拷贝都是把数据放在了寄存器中,此时要是想要使用我们形式参数的数据,那么需要编译器手动的取出.

c =a + b;
001E2EEC  mov         eax,dword ptr [ebp+8]  
001E2EEF  add         eax,dword ptr [ebp+0Ch]  
001E2EF2  mov         dword ptr [ebp-8],eax 

先例解释这一条,把ebp+8放在eax中.那么ebp+8是多少呢?答案就是我们的x值的拷贝

001E2EEC  mov         eax,dword ptr [ebp+8]  

image-20230301195651443

同理,这个命令是将ebp+0Ch的内容和eax加起来放到eax中.ebp+0Ch就是y值的拷贝

001E2EEF  add         eax,dword ptr [ebp+0Ch] 

image-20230301195843234

这里是将eax写入到ebp-8,也就是c中,就是加法计算的结果.

001E2EF2  mov         dword ptr [ebp-8],eax 

image-20230301200133091

返回值

其中我们函数栈帧是如何得到的已经了解了,我们来看函数栈帧的最后的一部分.

保存返回值

我们把计算出的结果保存到寄存器中,为了后面的使用

001E2EF5  mov         eax,dword ptr [ebp-8]  

image-20230301200656698

释放栈帧

此时我们函数已经结束了,现在是函数的后期处理工作,包含资源的清理.把ebp 覆盖到esp,也就是收缩栈顶到栈底,这一步可以称为“释放栈帧”,注意函数栈帧的释放可不是空间的释放,只不过是它变成无效,也就是释放栈帧是不等于释放空间的.

001E2F08  mov         esp,ebp  

image-20230301200918352

弹栈

此时发生了弹栈操作,将main函数的栈底放在ebp中,esp内容发生改变.记住了我们在开辟函数栈帧前已经把main函数栈底的地址压入栈中的,此时拿到就可以了.

001E2F0A  pop         ebp  // pop:数据弹出至指定位置,同时esp栈顶寄存器也要发生改变

image-20230301201210296

注意,此时我们还是处于MyAdd汇编语言的执行部分,此时我们应该返回原本mian函数调用函数的下一个地址,此时我们需要拿到我们曾经的保存在栈中的地址,压入到eip,要知道eip是编译器下一条要到的地址,esp内容改变

001E2F0B  ret  //类似pop eip命令

image-20230301202702935

释放空间

注意此时我们已经出了MyAdd函数的汇编语言了,已经开始了函数调用的后续工作,例如此时要释放临时拷贝的变量的空间,意思是esp+8放在esp中,也就是修改栈顶.

	z = MyAdd(x, y);
00AC1440  add         esp,8  
00AC1443  mov         dword ptr [z],eax  

image-20230301201756600

我们开始继续执行函数调用后的代码,将eax的值放到ebp-20,也就是z,后面mian函数的栈帧的销毁我也就不分析了,逻辑是一样的.

001E1E8A  mov         dword ptr [ebp-20h],eax 

总结

我来一个简单测总结,这个博客只要求大家理解就可以了.

  • 函数的的栈帧是由编译器决定的,C语言中很多数据类型,那么编译器有能力知道所有类型变量的大小。

  • push进去的变量的空间是连续的,所谓的push进入额变量就是 形参拷贝,压入返回值,压入main栈底的操作是连续的

  • 0CCh 的大小和你定义的函数的规模有关,编译器会自动计算.

我们看到了push变量空间是连续的,也就是我们可以通过形参的地址来修改另外的地址.

void MyAdd(int a, int b)
{
	printf("before: b = %d\n", b);
	*(&a + 1) = 100;
	printf("after : b = %d\n", b);
}

int main()
{
	int x = 0;
	int y = 0;
	MyAdd(x,y);
	return 0;
}

image-20230301222008329

那么此时也会做一些更加大胆的事情,例如修改mian函数的栈底和修改放回值,这就会让我们代码不能继续mian函数后续代码了,不过现在在VS是非常做测试了,它做的安全性更加强了.

如果面试官问你你可以讲解一下函数栈帧的底层是什么,可以简单的说一下流程吗?这里我给出下面自己的一个答案,注意回答问题的时候一定要说是自己根据观察VS系列编译器理解的,C语言中的函数栈帧创建流程通常包括以下几个步骤:

  1. 函数参数被压入栈中。在调用函数之前,函数的参数被压入栈中,按照从右到左的顺序进行压栈,
  2. 函数被调用时,系统会将函数的返回地址压入栈中,同时将栈顶指针(Stack Pointer,SP)减少相应的大小,顺便把mian函数的栈底压入栈中
  3. 函数局部变量被压入栈中。函数局部变量也是在栈中分配的,同样按照从右到左的顺序进行压栈。当函数返回时,这些局部变量所占用的栈空间也将被释放。
  4. 在执行函数体之前,为函数创建栈空间。调整栈指针的大小通常是在编译时计算出来的。
  5. 进行函数体的逻辑运算,包含变量的空间创建,使用形参的过程,返回值会被保存在一个寄存器中
  6. 释放栈帧,弹栈,返回地址被弹出栈中,释放形参的空间,调整栈顶,继续执行函数调用后面的代码.

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

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

相关文章

一些关于linux process 和python process的记录

python mulprocess 主要用来生成另一个进程并运行 def func(i):print(helloworld)from multiprocessing import Process p Process(targetfunc,args(i, )) p.start()如果想要调用shell命令&#xff0c;可以采用os.popen 或者是 subprocess.run 但是前者只能执行命令并获取输…

【链表OJ题(二)】链表的中间节点

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;数据结构 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录链表OJ题(二)1. 链表…

每日学术速递3.1

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.Directed Diffusion: Direct Control of Object Placement through Attention Guidance 标题&#xff1a;定向扩散&#xff1a;通过注意力引导直接控制物体放置 作者&#xff1a;…

Kafka入门(六)

下面聊聊Kafka中的Offset位移 1、Offset位移概述 在引入Kafka服务后&#xff0c;当consumer消费完数据后需要进行位移提交&#xff0c;那么提交的位移数据究竟存储到那里&#xff0c;有以何种方式进行存储&#xff1f; Kafka旧版本&#xff08;<0.8&#xff09;是重度依赖Z…

【python学习】批量从含有多列数据的txt文件中提取某个数据,并存入csv文件

批量从含有多列数据的txt文件中提取某个数据&#xff0c;并存入csv文件任务需求与解读代码实现导入相关库提取txt文件的三列数据存为列表按条件提取某个数据存入字典将字典写入csv文件任务需求与解读 昨天收到一个需求&#xff0c;希望能将电化学工作站的数据文件(.bin后缀)转…

欧文数据建模师 erwin Data Modeler Crack

欧文数据建模师 erwin Data Modeler 是一款屡获殊荣的数据建模工具&#xff0c; 用于查找、可视化、设计、部署和标准化高质量的企业数据资产。从任何地方发现和记录任何数据&#xff0c;以在大规模数据集成、主数据管理、元数据管理、大数据、商业智能和分析计划中实现一致性、…

kubernetes--安全沙箱运行容器gVisor

gVisor介绍 所知&#xff0c;容器的应用程序可以直接访问Linux内核的系统调用&#xff0c;容器在安全隔离上还是比较弱&#xff0c;虽然内核在不断的增强自身的安全特性&#xff0c;但由于内核自身代码极端复杂&#xff0c;CVE漏洞层出不穷。 所以要想减少这方面安全风险&#…

MATLAB | 这些花里胡哨的热图怎么画

好早之前写过一个绘制相关系数矩阵的代码&#xff0c;但是会自动求相关系数&#xff0c;而且画出来的热图只能是方形&#xff0c;这里写一款允许nan值出现&#xff0c;任意形状的热图绘制代码&#xff0c;绘制效果如下&#xff1a; 如遇到bug请后台提出&#xff0c;并去gitee下…

Spring Boot+Vue前后端分离项目练习02之网盘项目利用token进行登陆验证

1.添加依赖 首先需要添加jwt对应的依赖。 <dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency>2.添加配置 JWT由三部分构成&#xff0c;分别是 header, pa…

详解数据结构中的顺序表的手动实现,顺序表功能接口【数据结构】

文章目录线性表顺序表接口实现尾插尾删头插头删指定位置插入指定位置删除练习线性表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构&#xff0c;常见的线性表&#xff1a;顺序表、链表、栈、队列…

Freemarker动态模板渲染flyingsaucer将html转PDF(多页固定头尾)

目录一、序言二、CSS样式控制打印模板三、代码示例1、pom.xml2、application.yml3、PdfGenerationController4、Freemarker模板内容四、展示效果一、序言 一般正常来说&#xff0c;生成PDF的操作都是通过将HTML转成PDF&#xff0c;HTML动态渲染可以借助模板引擎&#xff0c;如…

从外行到外包,从手工测试到知名互联大厂测开,我经历了什么...

本人本科就读于某普通一本院校&#xff08;非985&#xff0c;211&#xff09;&#xff0c;经管类专业&#xff0c;从大四实习到15年毕业后前两年一直在从事自己专业相关的工作。17年时决定想要转业从事计算机相关领域工作&#xff0c;在17年9月的一个机遇大跨度转行到测试行业&…

vue子组件监听父组件数据变化并作出改变(亲测有效)

vue子组件监听父组件数据变化并作出改变&#xff08;亲测有效&#xff09; 1. 问题 1.1 封装组件时经常会遇到子组件需要根据父组件数据变化并执行对应的操作逻辑 1.2 监听方法中加了deep、immediate 等参数监听数组/对象还是没有生效 1.3 类型table组件需要根据父组件数据…

Java多线程学习——线程的创建、Thread类以及多线程状态

文章目录学习目标一、认识线程1、线程是什么&#xff1f;2、为什么要有线程3、进程和线程的区别二、Thread类以及常见方法1.创建线程的几种方式2、Thread类属性及方法2.1、Thread的常见构造方法2.2、Thread的常见属性3、线程的中断-interrupt()中断一个线程&#xff1a;4、等待…

前端面试题 —— 浏览器原理(一)

目录 一、进程与线程的概念 二、如何实现浏览器内多个标签页之间的通信? 三、浏览器资源缓存的位置有哪些&#xff1f; 四、对浏览器内核的理解 五、常见的浏览器内核比较 六、浏览器的主要组成部分 七、渲染过程中遇到 JS 文件如何处理&#xff1f; 八、什么情况会阻塞…

【C语言】动态内存管理

我们之前开辟的空间&#xff0c;大小固定&#xff0c;且在申明数组的时候&#xff0c;必须指定数组的长度。但是有时候我们需要的空间大小在程序运行的时候才知道&#xff0c;这就得动态内存开辟出马了。 目录 1.malloc和free 2.calloc 3.realloc 4.常见动态内存错误 5.经…

TCP 握手过程 三次 四次

蛋老师视频 SYN 同步 ACK 确认 FIN 结束 核心机制是确定哪些请求或响应需要丢弃 SYN、ACK、FIN 通过 1/0 设置开启/关闭 开启SYN后&#xff0c;报文中会随机生成 Sequence序号 用于校验 &#xff08;应用可能发起多个会话&#xff0c;可以区分&#xff09; 服务器的同步序…

2023版D盾防火墙v2.1.7.2,主动防御保护,以内外保护的方式 防止网站和服务器给入侵。限制了常见的入侵方法,让服务器更安全

v2.1.7.2 (20230107) 2023-1-7 1.修正PHP一处文件检测的bug。 2.修正某些情况下无法文件加白问题。 v2.1.7.2 2022-10-13 1.针对aspx的样本加入了新的识别。 2.针对上传 doc格式文件提示“上传格式不符” 的修正。 3.工具“HTTPS安全”,把 TLS 1.1 和 TLS 1.0 设置为默认不选中…

杰理AD16N简介

一、概述&#xff1a; AD16N是杰理新出的一个MP3解码芯片&#xff0c;是高集成度的 32 位通用音频 SOC&#xff0c; 集成 40KByte SRAM&#xff0c; 时钟源可选内部 RC 或外部12MHz 晶振&#xff0c; 最高主频可达 160MHz&#xff1b; 主要是替代AC109N系列和AC608N、AC104N系列…

Python爬虫书写时遇到的问题汇总

文章目录python的xpath插件需要的库下载出现问题懒加载python 爬取图片,网址都正确但是下不下来的原因:爬取下来的文字包含Windows不能识别的特殊字符selenium的find_element_by_id()出现的问题爬虫信息写入mysql时的1045号错误python的xpath插件需要的库下载出现问题 ERROR: C…