写在前面

  此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我

你如果是从中间插过来看的,请仔细阅读 羽夏看Win系统内核——简述 ,方便学习本教程。

  看此教程之前,问几个问题,基础知识储备好了吗?保护模式篇学会了吗?练习做完了吗?没有的话就不要继续了。


华丽的分割线


概述

  在64位下,有两种CPU模式,一种是IA-32e模式,是IA-32模式的扩展,另一个是Legacy模式。IA-32e模式是指内核64位,用户64或32位均可,它强制平坦段,不支持任务切换;而Legacy模式指内核32位,用户32位支持非平坦段、任务切换、虚拟8086、实模式等。

  在IA-32e模式下,代码段和数据段仍使用64位描述符,强制平坦(FSGS除外);TSS段描述符扩展到128位,TSS段不用来任务切换,主要保存一堆rsp备用指针;中断门描述符扩展到128位。

  有些寄存器我们需要知道的,如下所示:

MSR 寄存器名 索引数值
IA32_EFER_MSR C0000080H
IA32_FS_BASE C0000100H
IA32_GS_BASE C0000101H
IA32_KERNEL_GS_BASE C0000102H

IA32_EFER_MSR

  该MSR结构如下所示:

  索引0位表示SYSCALL/SYSRET这类指令是否启用,索引8位表示IA-32e模式是否启用,索引10位表示IA-32e模式是否处于处于活动状态,索引11位知识是否启用PAE分页的XD位是否有效。如果到这里有不会的地方,请回去复习。

  下面我们在虚拟机中读取该寄存器的值:

kd> rdmsr C0000080
msr[c0000080] = 00000000`00000d01
kd> .formats 00000000`00000d01
Evaluate expression:
Hex: 00000000`00000d01
Decimal: 3329
Octal: 0000000000000000006401
Binary: 00000000 00000000 00000000 00000000 00000000 00000000 00001101 00000001
Chars: ........
Time: Thu Jan 1 08:55:29 1970
Float: low 4.66492e-042 high 0
Double: 1.64474e-320

  从上面的值我们可以看出寄存器里面的所有的功能处于启用状态。

IA32_FS_BASE

  这个MSR寄存器保存的是fs的基址,由于是64位内核,直接读取的话该值是0,我们可以读取一下:

kd> rdmsr C0000100
msr[c0000100] = 00000000`00000000

IA32_GS_BASE

  这个MSR寄存器保存的是gs的基址,内核指向KPCR结构体(和32位的fs一样的作用),我们可以尝试以下:

kd> rdmsr C0000101
msr[c0000101] = fffff805`5e089000 kd> dt _KPCR fffff805`5e089000
nt!_KPCR
+0x000 NtTib : _NT_TIB
+0x000 GdtBase : 0xfffff805`65690fb0 _KGDTENTRY64
+0x008 TssBase : 0xfffff805`6568f000 _KTSS64
+0x010 UserRsp : 0
+0x018 Self : 0xfffff805`5e089000 _KPCR
+0x020 CurrentPrcb : 0xfffff805`5e089180 _KPRCB
+0x028 LockArray : 0xfffff805`5e089870 _KSPIN_LOCK_QUEUE
+0x030 Used_Self : 0x00000079`22c2a000 Void
+0x038 IdtBase : 0xfffff805`6568e000 _KIDTENTRY64
+0x040 Unused : [2] 0
+0x050 Irql : 0 ''
+0x051 SecondLevelCacheAssociativity : 0xc ''
+0x052 ObsoleteNumber : 0 ''
+0x053 Fill0 : 0 ''
+0x054 Unused0 : [3] 0
+0x060 MajorVersion : 1
+0x062 MinorVersion : 1
+0x064 StallScaleFactor : 0xb58
+0x068 Unused1 : [3] (null)
+0x080 KernelReserved : [15] 0
+0x0bc SecondLevelCacheSize : 0x900000
+0x0c0 HalReserved : [16] 0xad178600
+0x100 Unused2 : 0
+0x108 KdVersionBlock : (null)
+0x110 Unused3 : (null)
+0x118 PcrAlign1 : [24] 0
+0x180 Prcb : _KPRCB

IA32_KERNEL_GS_BASE

  这个MSR的作用是作为GS基址的交换目标。说人话的话就是一个缓存,交换比较方便。我们从3环到0环。从0环到3环,GS内核指向KPCR,3环指向TEB,是如何做到呢?这个就是一个非常重要的寄存器.进0环的时候,把3环的交给它;出0环的时候,把0环的交给它。

  我们来读一下:

kd> rdmsr C0000102
msr[c0000102] = 00000079`22c2a000

  按照上述说法,这个就是TEB了,但由于这个是3环的地址,我们无法读取里面的结构体内容。

  由于现在64位CPU一般安装的系统都是IA-32e模式下的,即内核64位的,故后续所有介绍都会以IA-32e模式为主。

段与段描述符

  在64位系统下,段的保护被进一步弱化。绝大多数的段被强制平坦。何为强制平坦,就是段的首地址和界限的值不再起作用,被保留置0。首地址默认是0,界限默认十六进制全是F。但也有例外,比如GSFS,因为它们指向一些结构体,这些结构体的首地址肯定不是0。

  64位的段和32位的段大体上就上述变化,但有一个段比较特别,那就是代码段,我们来看一下它的结构:

  代码段比32位的多了个位L,它的作用是来指示是32位的还是64位的,如果是0表示兼容模式(x86模式),为1则表示x64模式。

  里面还有一个D位,它的作用是指示默认大小的。如果L == 0,如果该位是0,默认的数据和地址大小为16位;反之为32位。如果L == 1D位必须为0,否则会触发通用保护异常。

  最后要介绍的段就是TSS段,被扩展为128位,用于满足寻址要求,如下结构所示:

  TSS和32位的操作系统一样,只是用来保存一堆寄存器的,并不用于任务切换。

  在64位下分页不再是PAE分页,而是被Intel称之为4级分页(4-LEVEL PAGING)的东西。当CR0.PG == 1 && CR4.PAE == 1 && IA32_EFER.LME == 1时会启用该分页模式。4级分页将48位线性地址转换为52位物理地址。虽然52位对应于4 PBytes,但线性地址仅限于48位,最多可以访问256 TBytes的线性地址空间。也就是说剩余的16位是被保留的。

  4级分页使用分页结构的层次结构来生成线性地址的转换。CR3用于定位第一个分页结构,即PML4表。将CR3与4级分页一起使用取决于是否已通过设置CR4.PCIDE启用进程上下文标识符。

  这个CR3也是结构的,并不直接是物理地址的,如果CR4.PCIDE为0,则它的结构如下:

  如果CR4.PCIDE为1,则它的结构如下:

  其中MMAXPHYADDR的缩写,最大是52,这个值通过CPUID.80000008H:EAX[7:0]进行查询,在我的虚拟机操作系统这个值为十进制的39

kd> r cr4
cr4=0000000000070678 kd> .formats 0000000000070678
Evaluate expression:
Hex: 00000000`00070678
Decimal: 460408
Octal: 0000000000000001603170
Binary: 00000000 00000000 00000000 00000000 00000000 00000111 00000110 01111000
Chars: .......x
Time: Tue Jan 6 15:53:28 1970
Float: low 6.45169e-040 high 0
Double: 2.27472e-318

  首先我们得确认这个CR4.PCIDE是在哪个位上,发现是在索引17位:

  这个位被置1了,说明是第二种形式。有关这部分细节,对于我们不写操作系统的人来说似乎没啥用处,就不追究了。

  下面我们来看看4级分页线性地址的结构,如下是线性地址如何翻译成物理地址的:

  按照之前的说法套路,给一个非正式的名字9-9-9-9-12分页。又了之前的底子,我就不赘述内部的详细结构,直接用一张图进行总结(在Intel白皮书的第2832页):

  细心的你可能会发现protection key这个东西,这是啥呢?这个就是4级分页的一个新的机制,提供更完全的保护,当CR4.PKE == 1时有效,不幸的是,在我的虚拟机的操作系统并不使用该位,所以无效,如果对这块技术感兴趣请仔细阅读Intel白皮书的第2835页。

  当然上述分页还有大页的情形,如下所示:

  下面我们就用x86下的实验方式来学习分页,首先你得有新版的CheatEngine,下面开始:

  如上图所示,我打开了一个CheatEngine和一个Notepad,并且在记事本里面写上hello,world!。然后我们开始老套路搜索内存:

  把,改为%,定位到指定的地址,如下图所示:

  我们先用vtop执行偷懒看看结果:

kd> !process 0 0 notepad.exe
PROCESS ffff8f0fec1be080
SessionId: 1 Cid: 091c Peb: 4e42d08000 ParentCid: 0b70
DirBase: 9bd65002 ObjectTable: ffffdd8291064c40 HandleCount: 356.
Image: notepad.exe kd> !vtop 9bd65000 1DEFB0044B0
Amd64VtoP: Virt 000001defb0044b0, pagedir 000000009bd65000
Amd64VtoP: PML4E 000000009bd65018
Amd64VtoP: PDPE 000000009d581bd8
Amd64VtoP: PDE 00000000a3882ec0
Amd64VtoP: PTE 00000000827ae020
Amd64VtoP: Mapped phys 0000000027fca4b0
Virtual address 1defb0044b0 translates to physical address 27fca4b0.
kd> !db 27fca4b0
#27fca4b0 68 00 65 00 6c 00 6c 00-6f 00 25 00 77 00 6f 00 h.e.l.l.o.%.w.o.
#27fca4c0 72 00 6c 00 64 00 21 00-0d 00 0a 00 00 00 00 00 r.l.d.!.........
#27fca4d0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#27fca4e0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#27fca4f0 00 00 00 00 00 00 00 00-08 00 79 fe de 01 00 00 ..........y.....
#27fca500 00 00 00 00 00 00 00 00-2a f8 73 48 50 71 00 10 ........*.sHPq..
#27fca510 30 48 00 fb de 01 00 00-00 42 00 fb de 01 00 00 0H.......B......
#27fca520 40 48 00 fb de 01 00 00-10 42 00 fb de 01 00 00 @H.......B......

  为什么我在遍历进程的后面加上notepad.exe呢?是因为在Win10上,就算你原装最低配置,也有一大串的进程,带来很大的烦恼。我明确是记事本的进程叫这个名字,我就在后面限定它。

  你或许还有一个疑问,为什么命名目录表基址的值为9bd65002,但为什么vtop的时候后面的2没了呢?那么我们再回过头来看看上图的Cr3结构你就会明白为什么。

  在做这个实验的时候,发现了一个十分有意思的现象,当你恢复虚拟机执行其他操作的时候,再次中断回到Windbg,注意我没有退出记事本,回来发现它的DirBase发生了变化。

  好,我们继续我们的实验:

kd> !process 0 0 notepad.exe
PROCESS ffff8f0fec1be080
SessionId: 1 Cid: 091c Peb: 4e42d08000 ParentCid: 0b70
DirBase: 9c065002 ObjectTable: ffffdd8291064c40 HandleCount: 359.
Image: notepad.exe kd> !vtop 9c065000 1DEFB0044B0
Amd64VtoP: Virt 000001defb0044b0, pagedir 000000009c065000
Amd64VtoP: PML4E 000000009c065018
Amd64VtoP: PDPE 0000000036881bd8
Amd64VtoP: PDE 00000000a3982ec0
Amd64VtoP: PTE 00000000828ae020
Amd64VtoP: Mapped phys 0000000027fca4b0

  好我们手动拆分虚拟地址并用访问物理地址的方式进行该实验。首先拆分以下我们的虚拟地址:

000000011 = 0x3
101111011 = 0x17B
111011000 = 0x1D8
000000100 = 0x4
010010110000 = 0x4B0

  拆解完毕后,我们来开始进行测试:

kd> !dq 9c065000
#9c065000 8a000000`13a79867 00000000`00000000
#9c065010 00000000`00000000 8a000000`36881867
#9c065020 00000000`00000000 00000000`00000000
#9c065030 00000000`00000000 00000000`00000000
#9c065040 00000000`00000000 00000000`00000000
#9c065050 00000000`00000000 00000000`00000000
#9c065060 00000000`00000000 00000000`00000000
#9c065070 00000000`00000000 00000000`00000000
kd> !dq 36881000+17b*8
#36881bd8 0a000000`a3982867 00000000`00000000
#36881be8 00000000`00000000 00000000`00000000
#36881bf8 00000000`00000000 00000000`00000000
#36881c08 00000000`00000000 00000000`00000000
#36881c18 00000000`00000000 00000000`00000000
#36881c28 00000000`00000000 00000000`00000000
#36881c38 00000000`00000000 00000000`00000000
#36881c48 00000000`00000000 00000000`00000000
kd> !dq A3982000+1D8*8
#a3982ec0 0a000000`828ae867 0a000000`27aa6867
#a3982ed0 0a000000`1222f867 00000000`00000000
#a3982ee0 00000000`00000000 00000000`00000000
#a3982ef0 00000000`00000000 00000000`00000000
#a3982f00 00000000`00000000 00000000`00000000
#a3982f10 00000000`00000000 00000000`00000000
#a3982f20 0a000000`99493867 0a000000`91a7b867
#a3982f30 00000000`00000000 00000000`00000000
kd> !dq 828ae000+4*8
#828ae020 81000000`27fca867 81000000`10be0867
#828ae030 81000000`892f5867 81000000`980f7847
#828ae040 81000000`2aef6867 81000000`93dfe867
#828ae050 81000000`12f06867 81000000`5210f867
#828ae060 81000000`98211867 81000000`a4310847
#828ae070 81000000`b011f867 81000000`2b920867
#828ae080 81000000`95d23847 81000000`6da22847
#828ae090 81000000`0e728847 81000000`88827867
kd> !db 27fca000+4b0
#27fca4b0 68 00 65 00 6c 00 6c 00-6f 00 25 00 77 00 6f 00 h.e.l.l.o.%.w.o.
#27fca4c0 72 00 6c 00 64 00 21 00-0d 00 0a 00 00 00 00 00 r.l.d.!.........
#27fca4d0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#27fca4e0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#27fca4f0 00 00 00 00 00 00 00 00-08 00 79 fe de 01 00 00 ..........y.....
#27fca500 00 00 00 00 00 00 00 00-2a f8 73 48 50 71 00 10 ........*.sHPq..
#27fca510 30 48 00 fb de 01 00 00-00 42 00 fb de 01 00 00 0H.......B......
#27fca520 40 48 00 fb de 01 00 00-10 42 00 fb de 01 00 00 @H.......B......

  当然上述我们有不严谨的地方,我们并没有判断是否是大页的情况,但对于一般变量来说,大页的情况还是比较少的。

  学过我之前写的教程都知道,操作系统可是不能这么直接访问物理内存的,它依旧通过线性地址进行访问内容,必然在某个地址放着这几张表。按照之前的学习路线,我们必然得需要逆向MmIsAddressValid,下面我们开始继续:

; BOOLEAN __stdcall MmIsAddressValid(PVOID VirtualAddress)
public MmIsAddressValid
MmIsAddressValid proc near ; CODE XREF: KiMarkBugCheckRegions+B3↑p
; KiMarkBugCheckRegions+1E8↑p ...
sub rsp, 28h
call MmIsAddressValidEx
add rsp, 28h
retn
MmIsAddressValid endp

  可以看出,这个啥也没做,只是调用了MmIsAddressValidEx这个函数,我们点进去看看:

; BOOLEAN __fastcall MmIsAddressValidEx(PVOID VirtualAddress)
MmIsAddressValidEx proc near ; CODE XREF: MiIncreaseUsedPtesCount+64↑p
; MiCommitExistingVad+65D↓p ... PXE = qword ptr -28h
PPE = qword ptr -20h
PDE = qword ptr -18h
PTE = qword ptr -10h
VirtualAddress = qword ptr 8 ; FUNCTION CHUNK AT .text:00000001402234DC SIZE 00000056 BYTES mov [rsp+VirtualAddress], rbx
push rdi
sub rsp, 20h
mov rax, rcx ; VirtualAddress
mov r10, rcx
sar rax, 47 ; 实现的虚拟地址是48位的,如果是内核地址,则最后是-1,用户地址是0
inc rax ; 自增1
cmp rax, 1 ; 如果大于1,有问题
ja InvalidAddr
mov rdx, rcx ; VirtualAddress
mov rdi, 0FFFFF68000000000h ; PML4 表基址
shr rdx, 9
mov rcx, 7FFFFFFFF8h
and rdx, rcx
mov rax, rdi
add rdx, rax
mov [rsp+28h+PXE], rdx ; 获取 PXE
shr rdx, 9
and rdx, rcx
mov rax, rdi
add rdx, rax
mov [rsp+28h+PPE], rdx ; 获取 PPE
shr rdx, 9
and rdx, rcx
mov rax, rdi
add rdx, rax
mov [rsp+28h+PDE], rdx ; 获取 PDE
shr rdx, 9
and rdx, rcx
mov rax, rdi
add rdx, rax
mov r11, 0FFFFF6FB7DBED000h
mov [rsp+28h+PTE], rdx ; 获取 PTE
mov edx, 4
mov rbx, 0FFFFF6FB7DBED7F8h loc_1400AB248: ; CODE XREF: MmIsAddressValidEx+BF↓j
mov r9, [rsp+rdx*8-8] ; 初始值 rdx = 4
dec rdx
mov rcx, [r9]
mov rax, r11
cmp r9, rax
jb short loc_1400AB263
mov rax, rbx
cmp r9, rax
jbe short loc_1400AB27E loc_1400AB263: ; CODE XREF: MmIsAddressValidEx+A9↑j
; MmIsAddressValidEx+D9↓j ...
test cl, 1
jz short InvalidAddr
test cl, cl
js short loc_1400AB2BD
test rdx, rdx
jnz short loc_1400AB248 loc_1400AB271: ; CODE XREF: MmIsAddressValidEx+113↓j
; MmIsAddressValidEx+122↓j
mov al, 1 loc_1400AB273: ; CODE XREF: MmIsAddressValidEx+126↓j
mov rbx, [rsp+28h+VirtualAddress]
add rsp, 20h
pop rdi
retn
; --------------------------------------------------------------------------- loc_1400AB27E: ; CODE XREF: MmIsAddressValidEx+B1↑j
mov eax, cs:MiFlags
test eax, 0C00000h
jz short loc_1400AB263
mov rax, gs:_KPCR.Prcb.CurrentThread
mov r8, [rax+_KTHREAD.___u25.ApcState.Process]
cmp [r8+_EPROCESS.Pcb.AddressPolicy], 1
jz short loc_1400AB263
test cl, 1
jz short InvalidAddr
test cl, 20h
jz loc_1402234DC
test cl, 42h
jnz short loc_1400AB263
jmp loc_1402234DC
; --------------------------------------------------------------------------- loc_1400AB2BD: ; CODE XREF: MmIsAddressValidEx+BA↑j
mov rax, rdi
cmp r10, rax
jb short loc_1400AB271
mov rax, 0FFFFF6FFFFFFFFFFh
cmp r10, rax
ja short loc_1400AB271 InvalidAddr: ; CODE XREF: MmIsAddressValidEx+1B↑j
; MmIsAddressValidEx+B6↑j ...
xor al, al
jmp short loc_1400AB273
MmIsAddressValidEx endp

  但不幸的是,我根本无法弄懂这块代码的所有细节,完全弄懂的部分均用注释标注,主要是如下代码看起来十分奇怪:

loc_1400AB248:                          ; CODE XREF: MmIsAddressValidEx+BF↓j
mov r9, [rsp+rdx*8-8] ; 初始值 rdx = 4
dec rdx
mov rcx, [r9]
mov rax, r11
cmp r9, rax
jb short loc_1400AB263
mov rax, rbx
cmp r9, rax
jbe short loc_1400AB27E

  根据堆栈图,它是在该函数直接提栈的局部变量区,而我未发现任何初始化该区域数据的汇编代码。该代码里面还有ShadowMapping这个东西,从名字上来看是影子映射相关的,但我同样弄不出来相关细节。网络上有关x64的资料少之又少,我无法解决,所以有关页的相关内容,只能在此告一段落。

  门在x86上是十分重要的东西,尤其是中断门在Win系统扮演了十分重要的角色。鉴于之前在基础篇保护模式介绍的内容基础,这里就只讲调用门和中断门。

  注意:我不太建议做和学WinXP一样的内核实验,因为PG有可能会被触发导致蓝屏,搞懂原理即可。

调用门

  在x64下,调用门的结构如下:

  可以看出,调用门被扩展为128位,基本内容并没有发生任何变化。我们看看GDT表里面的内容:

kd> dq gdtr
fffff803`86c90fb0 00000000`00000000 00000000`00000000
fffff803`86c90fc0 00209b00`00000000 00409300`00000000
fffff803`86c90fd0 00cffb00`0000ffff 00cff300`0000ffff
fffff803`86c90fe0 0020fb00`00000000 00000000`00000000
fffff803`86c90ff0 86008bc8`f0000067 00000000`fffff803
fffff803`86c91000 0040f300`00003c00 00000000`00000000
fffff803`86c91010 00000000`00000000 00000000`00000000
fffff803`86c91020 00000000`00000000 00000000`00000000

  里面的内容少了不少,相比于XP系统来说。有关调用门细节就介绍这些。

中断门

  中断门也被同样的扩展为128位,其余的功能并没有发生任何变化。

  然后我们再看一下IDT表:

kd> dq idtr
fffff803`86c8e000 81158e00`00101100 00000000`fffff803
fffff803`86c8e010 81158e04`00101180 00000000`fffff803
fffff803`86c8e020 81158e03`00101200 00000000`fffff803
fffff803`86c8e030 8115ee00`00101280 00000000`fffff803
fffff803`86c8e040 8115ee00`00101300 00000000`fffff803
fffff803`86c8e050 81158e00`00101380 00000000`fffff803
fffff803`86c8e060 81158e00`00101400 00000000`fffff803
fffff803`86c8e070 81158e00`00101480 00000000`fffff803

  我们可以用!idt获取每一个索引对应的函数,如下为部分展示,后面有stack的说明该函数有自己独占的栈。

kd> !idt

Dumping IDT: fffff80386c8e000

00: fffff80381151100 nt!KiDivideErrorFaultShadow
01: ffff80381151180 nt!KiDebugTrapOrFaultShadow Stack = 0xFFFFF80386C929D0
02: fffff80381151200 nt!KiNmiInterruptShadow Stack = 0xFFFFF80386C927D0
03: fffff80381151280 nt!KiBreakpointTrapShadow
04: fffff80381151300 nt!KiOverflowTrapShadow
05: fffff80381151380 nt!KiBoundFaultShadow
06: fffff80381151400 nt!KiInvalidOpcodeFaultShadow
07: fffff80381151480 nt!KiNpxNotAvailableFaultShadow
08: fffff80381151500 nt!KiDoubleFaultAbortShadow Stack = 0xFFFFF80386C923D0
……

  有关中断门相关内容,暂时就这么多。

SMEP 与 SMAP

  x64CPU又增加了一些标识来增加安全性:SMEPSMAP。这两个标识在CR4中,我们再拿来看看:

  SMEP英文全称是supervisor-mode execution prevention,看意思就是限制执行代码的。SMAP英文全称为supervisor-mode access prevention,看意思就是限制访问内存的。这些限制是限制谁的,限制内核的。在WinXP下,只要是内核就具有至高无上的权限,我想读哪块内存就读,想执行啥代码就执行。

  说这两个标志位的功能倒是挺容易的,为什么要说这个东西呢?之前的实验应用层程序通过调用门提权并执行代码就会出现问题,我们需要对这两个标志位进行清0操作,否则就会出错。

  有一个特殊的汇编指令CLAC,它的作用是清除EFLAGS寄存器中的AC标志位。这将禁用用户模式数据访问的任何对齐检查。如果在CR4寄存器中设置了SMAP位,这个位就不会起作用;STAC可以恢复使用SMAP。有关这两个位介绍就这么多。

下一篇

  x64 番外篇—— WOW64

x64 番外篇——保护模式相关的更多相关文章

  1. x64 番外篇——知识铺垫

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  2. 羽夏看Win系统内核—— x64 番外篇

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  3. 《手把手教你》系列技巧篇(三十一)-java+ selenium自动化测试- Actions的相关操作-番外篇(详解教程)

    1.简介 上一篇中,宏哥说的宏哥在最后提到网站的反爬虫机制,那么宏哥在自己本地做一个网页,没有那个反爬虫的机制,谷歌浏览器是不是就可以验证成功了,宏哥就想验证一下自己想法,于是写了这一篇文章,另外也是 ...

  4. 给深度学习入门者的Python快速教程 - 番外篇之Python-OpenCV

    这次博客园的排版彻底残了..高清版请移步: https://zhuanlan.zhihu.com/p/24425116 本篇是前面两篇教程: 给深度学习入门者的Python快速教程 - 基础篇 给深度 ...

  5. 同步IO与一部IO、IO多路复用(番外篇)select、poll、epoll三者的区别;blocking和non-blocking的区别 synchronous IO和asynchronous IO的区别

    Python之路,Day9 , IO多路复用(番外篇)   同步IO和异步IO,阻塞IO和非阻塞IO分别是什么,到底有什么区别?不同的人在不同的上下文下给出的答案是不同的.所以先限定一下本文的上下文. ...

  6. 羽夏看Win系统内核—— VT 入门番外篇

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  7. [uboot] (番外篇)uboot 驱动模型(转)重要

    [uboot] uboot流程系列:[project X] tiny210(s5pv210)上电启动流程(BL0-BL2)[project X] tiny210(s5pv210)从存储设备加载代码到D ...

  8. VT 入门番外篇——初识 VT

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  9. 【番外篇】ASP.NET MVC快速入门之免费jQuery控件库(MVC5+EF6)

    目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...

随机推荐

  1. Azure DevOps (三) 实现和Jenkins的联动

    上一篇文章中,我们通过azure 的webhook实现了和钉钉机器人的联动,实现了通过钉钉机器人告知大家刚才谁动了仓库. 在文章开篇的时候我们举例说了jenkins也可以实现和azure联动,今天我们 ...

  2. ABP Framework 5.2 RC 发布及新增功能介绍

    ABP Framework 5.2 RC 新增功能 目录 ABP Framework 5.2 RC 新增功能 单层解决方案模板 EF Core 数据库迁移 UI 和 数据库 选项 API 版本控制 源 ...

  3. Sequelize关联查询,where影响全局的解决

    需求背景 系统字段关联公司字段表,通过查询所有的系统字段然后关联查询指定的公司对应的字段. 问题代码 const sysField = ctx.model.SysDictionary; const c ...

  4. kkFileView对接svn服务完成文件在线预览功能

    1.需求: 之前在公司内部搭建了svn服务器,给部门存放文档.视频,做成了一个文档服务器来用,随着视频文件太大,每次下载太慢 需要把文件在线打开查看 2.解决: kkFileView https:// ...

  5. 详解java接口interface

    引言 接口这个词在生活中我们并不陌生. 在中国大陆,我们可以将自己的家用电器的插头插到符合它插口的插座上: 我们在戴尔,惠普,联想,苹果等品牌电脑之间传输数据时,可以使用U盘进行传输. 插座的普适性是 ...

  6. 面试官:Redis中的缓冲区了解吗

    hello 大家好,我是七淅(xī). Redis 大家肯定不陌生,但在使用层面看不到的地方,就容易被忽略.今天想和大家分享的内容是 Redis 各个缓冲区的作用.溢出的后果及优化方向. 在开始正文前 ...

  7. python神器 Jupyter Notbook

    python神器 Jupyter Notbook 简介 Jupyter Notebook是基于网页的用于交互计算的应用程序.其可被应用于全过程计算:开发.文档编写.运行代码和展示结果. Jupyter ...

  8. 随手用Java写的bilibili缓存视频转换器(合成分离的视频和音频)

    使用java随手写成,有需要可以自行修改. 项目使用到了fastjson,可以自行替换成其他json解析工具. 写这个的原因是因为下载到的其他工具弄出来的视频标题过长(应该取entry.json中的s ...

  9. .Net Core 前台添加赋值POST提交到Api控制实现添加

    使用Form表单提交到数据库 这两个必须要写,不能写这样的格式,会提交获取不到数据 contentType: 'application/json',正确格式: contentType: 'applic ...

  10. 16经典的SPI Flash的扇区擦除flash_se功能

    一设计功能 对SPI_flash进行扇区擦除,分为写指令和扇区擦除两个时序部分. 二设计知识点 我简单理解flash,第一它是掉电不丢失数据的存储器,第二它每次写入新数据前首先得擦除数据,分为扇区擦除 ...