自制操作系统日志——第二十二天

news/2024/4/29 16:03:38/文章来源:https://blog.csdn.net/qq_43696276/article/details/126185242

自制操作系统日志——第二十二天

今天,我们将继续再完善一下保护操作系统的内容,以及进一步的利用c语言显示字符串!


文章目录

  • 自制操作系统日志——第二十二天
  • 一、保护操作系统3
  • 手动强制关闭应用程序
  • 二、用c语言显示字符串
  • API 显示窗口
  • 总结


一、保护操作系统3

首先,让我们来继续验证一下昨天的应用程序的保护力度吧:
先总结一下:

  1. 不允许应用程序访问操作系统的段地址空间;
  2. 应用程序运行时,对in、out一异常保护的功能;
  3. 应用程序运行时,指向cli、sti、hlt等都会产生异常;
  4. 不允许应用程序任意使用call命令。

我们尝试对定时器进行破坏:

crack3.nas

[INSTRSET "i486p"]
[BITS 32]MOV		AL,0x34OUT		0x43,ALMOV		AL,0xffOUT		0x40,ALMOV		AL,0xffOUT		0x40,AL; 	上述代码与下面的相同
;	io_out8(PIT_CTRL, 0x34);
;	io_out8(PIT_CNT0, 0xff);
;	io_out8(PIT_CNT0, 0xff);MOV		EDX,4INT		0x40

在这里插入图片描述

crack4:

[INSTRSET "i486p"]
[BITS 32]CLI
fin:HLTJMP		fin

在这里插入图片描述

crack5:

[INSTRSET "i486p"]
[BITS 32]CALL	2*8:0xac1MOV		EDX,4INT		0x40

在这里插入图片描述

很好,能够很有效的进行了防范!但是呢,如果我们使用的api里含有严重的写入内存的bug的化,还是有可能会产生漏洞的。因为api这一个本身就是在操作系统内部运行的,而非应用程序段运行的,应用程序只是简单进行调用而已!!

那么,我们接下来再试一下:
bug1.c:

void api_putchar(int c);
void api_end(void);void HariMain(void)
{char a[100];a[10] = 'Y';api_putchar(a[10]);a[102] = 'U';api_putchar(a[102]);a[120] = 'A';api_putchar(a[120]);api_end();
}

然后,运行一下,嘿!果然出问题了,这里我在VMware中运行时,首先弹出YU然后系统马上就重启了。(重启是因为产生了没有设置过的异常导致的)

这里,由于a数组是存于栈空间的,因此这里很明显的异常就是栈溢出! 那么在进行下一步的更正前我先简介一下cpu的中断有哪些:

  • 0x00~0x1f 都是异常所使用的中断,通常有0x00除数异常;0x06非法指令异常;0x0c栈异常;0x0d应用程序异常
  • 0x20以后就是我们IRQ设置的部分了!!

接下来,让我们开始进行设置吧:
naskfun:

_asm_inthandler0c:STIPUSH 	ESPUSH 	DSPUSHADMOV		EAX,ESPPUSH	EAXMOV		AX,SSMOV		DS,AXMOV		ES,AXCALL	_inthandler0cCMP		EAX,0JNE		end_app	POP		EAXPOPADPOP		DSPOP		ESADD		ESP,4		;int 0x0c需要IRETD

console:

int *inthandler0d(int *esp)
{struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);struct TASK *task = task_now();cons_putstr0(cons, "\nINT 0C :\n Stack Exception.\n");return &(task->tss.esp0);	/*强制结束程序*/
}

dsctbl:

set_gatedesc(idt + 0x0c, (int) asm_inthandler0c, 2 * 8, AR_INTGATE32);

在这里插入图片描述

喔!貌似成功了,但是这里还有一点点小问题需要解释一下。即我们的U这里,也是明显超过了数组a的界限,为什么还能显示出来呢?

其实这里,0x0c这个异常仅仅只判断是否超过了应用程序分配的数据段的边界。也就是说我们的A是超过了分配的边界,而U虽然也超过了数组a的边界是个bug,但是他这里还没超过系统为应用程序分配的数据段的边界,因此这里不报错!

那么我们做一个定位的小程序吧:

int *inthandler0d(int *esp)
{struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);struct TASK *task = task_now();char s[30];cons_putstr0(cons, "\nINT 0D :\n General Protected Exception.\n");sprintf(s, "EIP = %08X\n", esp[11]);cons_putstr0(cons, s);return &(task->tss.esp0);	/*强制结束程序*/
}int *inthandler0c(int *esp)
{struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);struct TASK *task = task_now();char s[30];cons_putstr0(cons, "\nINT 0C :\n Stack Exception.\n");sprintf(s, "EIP = %08X\n", esp[11]);cons_putstr0(cons, s);return &(task->tss.esp0);	/*强制结束程序*/
}

这里就是☞将esp的第十一号元素显示出来:
在这里插入图片描述

呃呃呃,图片里有我画的笔记,嘿嘿嘿别介意!! 然后我们来看看结果:

在这里插入图片描述

这里,显示了eip = 0042,那么我们来看看map和list:
list
在这里插入图片描述

map:
在这里插入图片描述

这里,list是还链接时的地址,因此暂时用0000代替。而map是连接后各个的地址。首先从map中可以看出我们的异常地址位于主函数中,然后看看主函数里的代码长度,发现了 0x24 + 0x1e = 0x42 因此,我们就定位到了a[120] = ‘A’ 处!
在这里插入图片描述

手动强制关闭应用程序

假设我们有如下bug:

void api_putchar(int c);
void api_end(void);void HariMain(void)
{for (;;) {api_putchar('a');}
}

如果我们,不能强制关闭的话,那么这个程序会一直循环导致程序一直占用系统资源的!
那么接下来我们就制作一个强制结束的程序,主要利用前面的end_app这个函数,不过这里我们要稍作修改:

bootpack.c:

				   if(i == 256 + 0x3b && key_shift != 0 && task_cons->tss.ss0 != 0){//shift + F1强制关闭cons = (struct CONSOLE *) *((int *) 0xfec);cons_putstr0(cons, "\nBreak(key):\n");io_cli();//不能再改变寄存器的值时候进行切换task_cons->tss.eax = (int) &(task_cons->tss.esp0);task_cons->tss.eip = (int) asm_end_app;io_sti();}
  1. 这里我们选用主函数的原因是因为,当我们输入上述恶意代码后,命令行窗口会被占用,无法输入任何字符,因此也无法进行终止,想来想去,目前只有主函数可以随时传送数据进入;
  2. 上述程序的工作原理是:当我们接收到来自键盘的中断请求后,判断进入缓冲区的是否是shift+F1,并且还需要判断命令行窗口是否运行着程序,如果运行且接收到shif+F1,则:
  3. 改写命令行窗口任务的寄存器值,将asm_end_app的地址放入下一条执行命令里,以便执行该函数里的内容,并且将esp0也就是代码段地址的值送入eax中。
  4. 任何end_app这个函数接收到后进行把该值再赋予esp,任何返回到命令行窗口。

naskfunc.nas:

_asm_end_app:
;	EAX为tss.esp0的地址MOV		ESP,[EAX]MOV 	DWORD [EAX+4],0POPADRET			; 返回cmd_app

mtask.c:

struct TASK *task_alloc(void)
{
略task->tss.iomap = 0x40000000;task->tss.ss0 = 0 ;}

运行看看:
在这里插入图片描述

二、用c语言显示字符串

首先,我们写入可供调用的api:
a_nask.nas:

_api_putstr0:  ; void api_putstr0(char *s);PUSH	EBXMOV		EDX,2MOV		EBX,[ESP+8]		;SINT		0x40POP		EBXRET

然后编写一下c文件:

void api_putstr0(char *s);
void api_end(void);void HariMain(void)
{api_putstr0("hello, world\n");api_end();
}

这里,还有一点需要修改的就是,由于我们之前已经启用了x86架构对于异常保护的特性,因此不能利用retf来结束应用程序,那么我们之前对于"Hari" 开头这里也需要修改一下:

start_app(0x1b, 1003 * 8, esp, 1004 * 8, &(task->tss.esp0));

可以直接,指定应用程序的hari的起始地址来代替前面的替换。好了让我们试试看?

哎!这里貌似有点小问题,我尝试了几次,有时是显现不出来,有时是乱码。这是怎么一回事呢? 那由于我们再前面用的是EBX进行传递字符串地址的,那现在让我们看看EBX寄存器里保存的值是多少吧:

int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{char s[12];else if (edx == 2) {cons_putstr0(cons, (char *) ebx + cs_base);sprintf(s,"%08X\n", ebx);cons_putstr0(cons, s);}}

然后,运行一下看看,发现ebx的值为0x00000400,也就是1024字节,可是这大小远远超过了我们程序段的范围了呀!

那让我们分析一下hrb这个文件吧:
由bim2hrb这个生成的文件有两部分:

  • 代码部分;
  • 数据部分。

当程序中没有使用字符串和外部变量时候,就会生成不包含数据部分的hrb文件。不过,我们之前很少考虑到数据部分这个位置的。而我们上述的程序在连接时候bim2hrb这个程序直接就认为了我们的字符串应该是在0x400这个部分。(具体的应该是编译器的设计问题吧)。 那么既然我们要开始考虑这部分的内容了,就让我们看一下hrb文件开头有哪些内容吧:

在这里插入图片描述

在0x0018这里, e9这个在汇编语言里的意思就是jmp。结合后面的就是jmp 应用程序入口。

至此,我们来修改一下程序:

int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{int i, segsiz, datsiz, esp, dathrb;if (finfo != 0) {/*找到文件的情况*/p = (char *) memman_alloc_4k(memman, finfo->size);file_loadfile(finfo->clustno, finfo->size, p, fat, (char *)(ADR_DISKIMG + 0x003e00));if (finfo->size >= 36 && strncmp(p + 4, "Hari", 4) == 0 && *p == 0x00){segsiz = *((int *) (p + 0x0000)); //应用程序的数据段大小esp    = *((int *) (p + 0x000c));datsiz = *((int *) (p + 0x0010));dathrb = *((int *) (p + 0x0014));q = (char *) memman_alloc_4k(memman, segsiz);*((int *) 0xfe8) = (int) q;set_segmdesc(gdt + 1003, finfo->size - 1, (int) p, AR_CODE32_ER + 0x60);set_segmdesc(gdt + 1004,      segsiz - 1, (int) q, AR_DATA32_RW + 0x60);for(i = 0; i < datsiz; i++){//将hrb文件的数据部分复制到数据段中后再启动appq[esp + i] = p[dathrb + i];}start_app(0x1b, 1003 * 8, esp, 1004 * 8, &(task->tss.esp0));memman_free_4k(memman, (int) q, segsiz);}else{cons_putstr0(cons, ".hrb file format error.\n");}memman_free_4k(memman, (int) p, finfo->size);cons_newline(cons);return 1;}
return 0}

本次,修改的地方主要针对以下3点:

  • 文件中找不到"Hari"标志就报错;
  • 数据段大小根据.hrb文件中指定的值进行分配;
  • 将hrb文件的数据部分复制到数据段后再启动程序。

然后,我们再写一个nas的程序看看:
hello5.nas

[FORMAT "WCOFF"]
[INSTRSET "i486p"]
[BITS 32]
[FILE "hello5.nas"]GLOBAL	_HariMain[SECTION .text]_HariMain:MOV		EDX,2MOV		EBX,msgINT		0x40MOV		EDX,4INT		0x40[SECTION .data]msg:DB	"hello, world", 0x0a, 0

这里的section就是指将代码段和数据段进行区分开来。除此之外,一般可执行文件最后都要加上一些类似"Hari”这个标志,比如说exe开头也有MZ这个标志,以此来进行标识的作用。
在这里插入图片描述

好了,现在让我们make run看看:
在这里插入图片描述

成功了!嘿嘿嘿,很好继续加油。

API 显示窗口

接下来,让我们调用API来显示一个窗口吧,具体我们主要做如下的设置:

  • EDX = 5;
  • EBX = 窗口缓冲区;
  • ESI = x轴大小,即窗口宽度;
  • EDI = y轴大小,即窗口高度;
  • EAX = 透明色;
  • ECX = 窗口名称;
    调用后,返回值如下:
  • EAX = 操作窗口的句柄(例如刷新操作)

于是按照这个思路进行如下改写:
console.c:

int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{char s[12];int ds_base = *((int *) 0xfe8);struct TASK *task = task_now();struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);struct SHTCTL *shtctl = (struct SHTCTL *) *((int *) 0x0fe4);//bootpack中的值struct SHEET *sht;int *reg = &eax + 1;//指向eax后面的地址,是因为asm_hrb_api中push了两次一模一样的寄存器的值/* reg[0] : EDI,   reg[1] : ESI,   reg[2] : EBP,   reg[3] : ESP *//* reg[4] : EBX,   reg[5] : EDX,   reg[6] : ECX,   reg[7] : EAX */if (edx == 1) {cons_putchar(cons, eax & 0xff, 1);} else if (edx == 2) {cons_putstr0(cons, (char *) ebx + ds_base);sprintf(s,"%08X\n", ebx);cons_putstr0(cons, s);} else if (edx == 3) {cons_putstr1(cons, (char *) ebx + ds_base, ecx);} else if (edx == 4) {return &(task->tss.esp0);}else if (edx == 5){sht = sheet_alloc(shtctl);sheet_setbuf(sht, (char *) ebx + ds_base, esi, edi, eax);//设置x、y轴和透明色make_window8((char *)ebx + ds_base, esi, edi, (char *) ecx + ds_base, 0);sheet_slide(sht, 100, 50);sheet_updown(sht, 3);//高度高于task_areg[7] = (int) sht;//方便后续向应用程序的返回值动手脚}return 0;
}

bootpack.c:

	task_run(task_a, 1, 2);*((int *) 0x0fe4) = (int) shtctl;

测试程序:
a_nask.nas:

_api_openwin:	; int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);PUSH	EDIPUSH	ESIPUSH	EBXMOV		EDX,5MOV		EBX,[ESP+16]	; bufMOV		ESI,[ESP+20]	; xsizMOV		EDI,[ESP+24]	; ysizMOV		EAX,[ESP+28]	; col_invMOV		ECX,[ESP+32]	; titleINT		0x40POP		EBXPOP		ESIPOP		EDIRET

winhelo.c:

int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_end(void);char buf[150 * 50];void HariMain(void)
{int win;win = api_openwin(buf, 150, 50, -1, "Yuan-os");api_end();
}

然后,运行:
在这里插入图片描述

哦耶!!🤞

那么我们继续进一步的对窗口增加一些功能:

窗口中显示字符API:

  • EDX = 6
  • EBX = 窗口句柄
  • ESI = 显示位置的x坐标
  • EDI = 显示位置的y坐标
  • EAX = 色号
  • ECX = 字符串长度
  • EBP = 字符串

窗口中显示方块的API:

  • EDX = 7
  • EBX = 窗口句柄
  • EAX = x0
  • ECX = y0
  • ESI = x1
  • EDI = y1
  • EBP = 色号

接下来就开始描写吧:
console.c:

else if (edx == 6) {sht = (struct SHEET *) ebx;putfonts8_asc(sht->buf, sht->bxsize, esi, edi, eax, (char *) ebp + ds_base);sheet_refresh(sht, esi, edi, esi + ecx * 8, edi + 16);} else if (edx == 7) {sht = (struct SHEET *) ebx;boxfill8(sht->buf, sht->bxsize, ebp, eax, ecx, esi, edi);sheet_refresh(sht, eax, ecx, esi + 1, edi + 1);}

a_nask.nas:


_api_putstrwin:	; void api_putstrwin(int win, int x, int y, int col, int len, char *str);PUSH	EDIPUSH	ESIPUSH	EBPPUSH	EBXMOV		EDX,6MOV		EBX,[ESP+20]	; winMOV		ESI,[ESP+24]	; xMOV		EDI,[ESP+28]	; yMOV		EAX,[ESP+32]	; colMOV		ECX,[ESP+36]	; lenMOV		EBP,[ESP+40]	; strINT		0x40POP		EBXPOP		EBPPOP		ESIPOP		EDIRET_api_boxfilwin:	; void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);PUSH	EDIPUSH	ESIPUSH	EBPPUSH	EBXMOV		EDX,7MOV		EBX,[ESP+20]	; winMOV		EAX,[ESP+24]	; x0MOV		ECX,[ESP+28]	; y0MOV		ESI,[ESP+32]	; x1MOV		EDI,[ESP+36]	; y1MOV		EBP,[ESP+40]	; colINT		0x40POP		EBXPOP		EBPPOP		ESIPOP		EDIRET

程序:

int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_putstrwin(int win, int x, int y, int col, int len, char *str);
void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
void api_end(void);char buf[150 * 50];void HariMain(void)
{int win;win = api_openwin(buf, 150, 50, -1, "hello");api_boxfilwin(win,  8, 36, 141, 43, 3 /* 黄色*/);api_putstrwin(win, 28, 28, 0 /* 黑色*/, 12, "hello, world");api_end();
}

然后运行看看:

在这里插入图片描述


总结

呼呼,终于完成了,说实话,这一天的内容我实际上花了好久,因为身心以及难度来说,都令我感到疲倦了。不过休整了一下明天继续加油,还有几天我们就可以大功告成了!!

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vivado使用方法(初级)

文章目录1 创建新工程1.1 工程创建1.2 新建Verilog文件1.3 仿真参考1 创建新工程 1.1 工程创建 1、首先打开Vavido软件&#xff0c;点击Creat Project或者在File——>Project——>New里面进行新工程的创建 2、然后在弹出的界面上点击Next进入下一个界面进行项目的命名…

全站最简单 “数据滚动可视化大屏” 【JS基础拿来即用】

源码获取方式&#xff1a; 数据滚动大屏源码&#xff0c;原生js实现超级简单-Javascript文档类资源-CSDN下载原生js实现的数据滚动大屏案例&#xff0c;实现应该是全网最简单的&#xff0c;拿来直接使用即可&#xff0c;没有会员的小伙伴去我文章主更多下载资源、学习资料请访问…

基于Python实现的遗传算法求TSP问题

遗传算法求TSP问题 目录 人工智能第四次实验报告 1 遗传算法求TSP问题 1 一 、问题背景 1 1.1 遗传算法简介 1 1.2 遗传算法基本要素 2 1.3 遗传算法一般步骤 2 二 、程序说明 3 2.3 选择初始群体 4 2.4 适应度函数 4 2.5 遗传操作 4 2.6 迭代过程 4 三 、程序测试 5 3.1 求解…

Vue3+elementplus搭建通用管理系统实例七:通用表格实现上

一、本章内容 使用配置的方式实现表格的界面的自动生成、自动解析实体配置信息,并生成表格列、筛选项等功能,完整课程地址 二、效果预览 三、开发视频

动手实现深度学习(12): 卷积层的实现

9.1 卷积层的运算 传送门: https://www.cnblogs.com/greentomlee/p/12314064.html github: Leezhen2014: https://github.com/Leezhen2014/python_deep_learning 卷积的forward 卷积的计算过程网上的资料已经做够好了,没必要自己再写一遍。只把资料搬运到这里: http://deepl…

【进击的JavaScript|高薪面试必看】JS基础-作用域和闭包

六年代码两茫茫&#xff0c;不思量&#xff0c;自难忘 6年资深前端主管一枚&#xff0c;只分享技术干货&#xff0c;项目实战经验&#xff0c;面试指导 关注博主不迷路~ 本系列文章是博主精心整理的面试热点问题&#xff0c;吸收了大量的技术博客与面试文章&#xff0c;总结多年…

Java毕设项目——网上宠物店管理系统(java+SSM+Maven+Mysql+Jsp)

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM 技术&#xff1a;Jsp JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7/8.0 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a…

收银台——Web自动化测试

目录 一&#xff0c;收银台项目的主要功能&#xff1a; 二&#xff0c;Web自动化测试 一&#xff0c;Web自动化测试&#xff0c;设计测试用例 二&#xff0c;编写测试用例代码 三&#xff0c;测试结果&#xff1a; 四&#xff0c;总结&#xff1a; 一&#xff0c;收银台项…

JVM监控:JMX组件与底层原理

JMX(Java Management Extensions)是一个为应用程序植入管理功能的框架 &#xff0c;从Java5.0开始引入到标准Java技术平台中。JMX是一套标准的代理和服务&#xff0c;实际上&#xff0c;用户可以在任何Java应用程序中使用这些代理和服务实现管理。 其实JMX也可以看作一个框架&a…

一建报名重大变动 部分专业考生2022年不能报名一级建造师考试?

2022年度一级建造师资格考试报名证明事项实行告知承诺制&#xff0c;应试人员须通过中国人事考试网的全国专业技术人员资格考试报名服务平台进行网上注册、报名和缴费。 云南报名时间&#xff1a;2022年9月14日—9月21日&#xff1b;缴费截止时间&#xff1a;9月14日—9月23日…

【Android】App开发-控件篇

App开发是一个工作量比较大的项目&#xff0c;要学习App开发首先我们要先去学习手机中的各类工具和信息是怎么运行的&#xff0c;我们可以使用哪些工具来对手机进行设置。这里我采用的开发工具是Android studio。 目录 Textview控件 文本框控件&#xff1a; 阴影/模糊度控件…

第12章 软件测试基础 12.1-软件测试 12.2-验证与确认 12.3-软件缺陷

目录 一、软件测试基础主要内容 二、软件测试 1、软件测试的定义 2、软件测试的对象 3、软件测试的目的 4、考点 &#xff08;1&#xff09;软件测试的目的 &#xff08;2&#xff09;软件测试的对象 三、验证与确认 1、验证&#xff08;Verification&#xff09; 2、确认&…

pycharm安装opencv-python报错

嘿嘿&#xff0c;大家好&#xff0c;我又遇到拦路的小可爱了&#xff01; 报错内容 3): Read timed out. WARNING: You are using pip version 21.3.1; however, version 22.2.2 is available. You should consider upgrading via the E:\daimabao\python\bigdata\Scripts\pyt…

【小月电子】安路国产FPGA开发板系统学习教程-LESSON7串口通信

串口通信例程讲解若要观看该博客配套的视频教程&#xff0c;可点击此链接根据多年工作经验&#xff0c;总结出的FPGA的设计流程&#xff0c;概括起来总共有以上12步&#xff0c;其中根据项目难易度可省去其中一些步骤。比如非常简单的项目&#xff0c;我们可以省去虚线框里面的…

【数据结构】二叉树的遍历

文章目录 5.3 二叉树的遍历 5.3.1 概述 5.3.2 遍历方式【重点】 5.3.3 遍历方式&#xff1a;递归实现【重点】 5.3.4 遍历方式&#xff1a;非递归实现 5.3 二叉树的遍历 5.3.1 概述 二叉树的遍历&#xff1a;沿着某条搜索路径对二叉树中的结点进行访问&#xff0c;使得每…

grpc|protobuf的安装、编译、运行笔记(C++)

一、下载grpc源码 如果你的电脑/服务器可以做代理&#xff0c;然后稳定链接上 GitHub 那么完全可以按照 GitHub 的官方文档来操作&#xff0c;我这里采用 Gitee 镜像来操作 git clone https://gitee.com/jiangxy__loey/grpc.git二、下载依赖库 进入grpc目录&#xff0c;然后…

为什么残差连接的网络结构更容易学习?

为什么残差连接的网络结构更容易学习&#xff1f; 【写在前面】 不仅仅在resnet中&#xff0c;在各种网络结构中大家都喜欢使用残差连接的设计&#xff0c;并声称这有利于网络的优化&#xff0c;这是为什么呢&#xff1f;能给出一个有说服力的答案吗&#xff1f; Why the re…

1.数据校验-拦截器-全局异常-json数据处理

目录 1.数据校验-拦截器-全局异常-json数据处理 1. JSR303 2. JSR303中含有的注解 3. spring中使用JSR303进行服务端校验 3.1 导入依赖包 3.2 添加验证规则 3.3执行校验 3.4 错误信息的展示 4. SpringMVC定义Restfull接口 5.1 增加spring配置 5.2 Controller 5.3 格…

Mstsc(远程桌面连接)命令的高级用法

Mstsc远程桌面连接,这个是微软操作系统自带的一个命令,相信很多人都用过,但是如果说这个命令还有高级用法,估计很多人都没有用过,其实这个命令还是很强大的,今天咱们就来说一下mstsc的高级用法Mstsc远程桌面连接,这个是微软操作系统自带的一个命令,相信很多人都用过,但…

20220912--CSP-S模拟4

A. 石子游戏 B. 大鱼吃小鱼 C. 黑客 D. 黑客-续A. 石子游戏 首先了解一个叫做 \(\operatorname{Nim}\) 游戏的玩意 通常的 \(\operatorname{Nim}\) 游戏的定义是这样的: 有若干堆石子,每堆石子的数量都是有限的,合法的移动是“选择一堆石子并拿走若干颗(不能不拿)” 如果轮…