From c09af4c65ff540a77351bb60466b2987ae8a35b2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 24 Feb 2026 13:31:26 +0000 Subject: [PATCH] archive: add 5 repo prompt(s) [skip ci] --- archive/supermanc88/Document.txt | 42343 +++++++++++++++++++++++++++++ 1 file changed, 42343 insertions(+) create mode 100644 archive/supermanc88/Document.txt diff --git a/archive/supermanc88/Document.txt b/archive/supermanc88/Document.txt new file mode 100644 index 00000000..848d7e95 --- /dev/null +++ b/archive/supermanc88/Document.txt @@ -0,0 +1,42343 @@ +Project Path: arc_supermanc88_Document_6aaqgw9u + +Source Tree: + +```txt +arc_supermanc88_Document_6aaqgw9u +├── ASM +│ ├── C调用汇编.md +│ ├── Linux下C和ASM混编.md +│ ├── 标志寄存器.md +│ ├── 调用约定.md +│ └── 除法逆向.md +├── C++ +│ ├── C++构造函数和析构函数.md +│ ├── C++虚函数.md +│ ├── Resharp快捷键汇总.md +│ ├── images +│ │ ├── 函数模板 +│ │ │ └── image-20200520182738355.png +│ │ └── 类模板 +│ │ └── image-20200520183250293.png +│ ├── 函数名是函数地址吗.md +│ ├── 函数模板.md +│ ├── 类模板.md +│ └── 重载.md +├── CMake +│ ├── CMake.md +│ ├── makefile.md +│ └── 编译ollvm.md +├── Docker +│ └── Docker安装.md +├── GO +│ ├── Go第三方静态库使用方式.docx +│ ├── go语言静态库的编译和使用.md +│ ├── images +│ │ ├── 工作区和GOPATH +│ │ │ ├── 2fdfb5620e072d864907870e61ae5f3c.png +│ │ │ └── image-20210128150559844.png +│ │ └── 程序实体的那些事 +│ │ └── 5e68210d5639f9e42738f21bd9eb1e89.png +│ ├── 命令源码文件.md +│ ├── 工作区和GOPATH.md +│ ├── 库源码文件.md +│ └── 程序实体的那些事.md +├── GameMaker +│ └── The Asset Editors +│ ├── Animation Curve.md +│ ├── Objects +│ │ ├── Object Events +│ │ │ ├── Async Events +│ │ │ │ ├── Audio Playback.md +│ │ │ │ └── images +│ │ │ │ └── Audio Playback +│ │ │ │ └── Async_AudioPlayback.png +│ │ │ ├── Draw Events.md +│ │ │ ├── Event Order.md +│ │ │ ├── Gesture Events.md +│ │ │ ├── Other Events.md +│ │ │ └── images +│ │ │ ├── Draw Events +│ │ │ │ ├── Editor_Events_DrawOrder.png +│ │ │ │ ├── Editor_Object_PostDraw.png +│ │ │ │ └── Editor_Objects_Draw.png +│ │ │ ├── Gesture Events +│ │ │ │ ├── Editor_Objects_Gesture.png +│ │ │ │ ├── Icon_Drag.png +│ │ │ │ ├── Icon_Flick.png +│ │ │ │ ├── Icon_Pinch.png +│ │ │ │ ├── Icon_Rotate.png +│ │ │ │ └── Icon_Tap.png +│ │ │ └── Other Events +│ │ │ └── Editor_Objects_OtherEvents.png +│ │ ├── Object Variables.md +│ │ ├── Parent Objects.md +│ │ ├── Physics Objects.md +│ │ └── images +│ │ ├── Object Variables +│ │ │ ├── Editor_Object_ChildVariables.png +│ │ │ ├── Editor_Object_ListArray.png +│ │ │ ├── Editor_Object_ListOptions.png +│ │ │ ├── Editor_Object_ListType.png +│ │ │ ├── Editor_Object_NewVariable.png +│ │ │ ├── Editor_Object_RealOptions.png +│ │ │ ├── Editor_Object_VariableRange.png +│ │ │ ├── Editor_Object_VariableTypes.png +│ │ │ ├── Editor_Object_Variables.png +│ │ │ ├── Icon_DeleteVariable.png +│ │ │ ├── Icon_InheritedVariable.png +│ │ │ ├── Icon_LMB.png +│ │ │ ├── Icon_OpenAssetExplorer.png +│ │ │ ├── Icon_OverrideVariable.png +│ │ │ └── Icon_VariableOptions.png +│ │ ├── Parent Objects +│ │ │ ├── Editor_Object_InheritEvents.png +│ │ │ ├── Editor_Object_ParentCollision.png +│ │ │ ├── Editor_Object_ParentOverride.png +│ │ │ ├── Editor_Object_Parent_Menu.png +│ │ │ ├── Editor_Objects_Parent.png +│ │ │ ├── Icon_LMB.png +│ │ │ └── Icon_RMB.png +│ │ └── Physics Objects +│ │ ├── Editor_Object_PhysicsShape.png +│ │ ├── Editor_Objects_Physics.png +│ │ └── Editor_Objects_PhysicsShape.png +│ ├── Paths.md +│ ├── Rooms +│ │ ├── Layer Properties.md +│ │ ├── Room Inheritance.md +│ │ ├── Room Properties.md +│ │ └── images +│ │ ├── Layer Properties +│ │ │ ├── Editor_Room_AssetProperties.png +│ │ │ ├── Editor_Room_BackgroundProperties.png +│ │ │ ├── Editor_Room_InstanceProperties.png +│ │ │ ├── Editor_Room_Instances.png +│ │ │ ├── Editor_Room_LayerAssetProperties.png +│ │ │ ├── Editor_Room_LayerSubMenu.png +│ │ │ ├── Editor_Room_Layers.png +│ │ │ ├── Editor_Room_PathProperties.png +│ │ │ ├── Editor_Room_PathRMBMenu.png +│ │ │ ├── Editor_Room_TileEditor.png +│ │ │ ├── Icon_Alt.png +│ │ │ ├── Icon_Assets.png +│ │ │ ├── Icon_Background.png +│ │ │ ├── Icon_Cmd.png +│ │ │ ├── Icon_Ctrl.png +│ │ │ ├── Icon_DeleteLayer.png +│ │ │ ├── Icon_Escape.png +│ │ │ ├── Icon_Eye.png +│ │ │ ├── Icon_Folder.png +│ │ │ ├── Icon_Instance.png +│ │ │ ├── Icon_LMB.png +│ │ │ ├── Icon_Padlock.png +│ │ │ ├── Icon_Paths.png +│ │ │ ├── Icon_RMB.png +│ │ │ ├── Icon_Shift.png +│ │ │ ├── Icon_Space.png +│ │ │ ├── Icon_TileSelect.png +│ │ │ ├── Icon_Tile_Auto.png +│ │ │ ├── Icon_Tile_Draw.png +│ │ │ ├── Icon_Tile_Erase.png +│ │ │ ├── Icon_Tile_Fill.png +│ │ │ ├── Icon_Tile_Flip.png +│ │ │ ├── Icon_Tile_Inherit.png +│ │ │ ├── Icon_Tile_Line.png +│ │ │ ├── Icon_Tile_Mirror.png +│ │ │ ├── Icon_Tile_Rectangle.png +│ │ │ ├── Icon_Tile_Rotate.png +│ │ │ └── Icon_Tilemap.png +│ │ ├── Room Inheritance +│ │ │ ├── Editor_RoomInheritance.png +│ │ │ ├── Editor_Room_Inheritance.png +│ │ │ ├── Icon_LMB.png +│ │ │ └── Icon_RMB.png +│ │ └── Room Properties +│ │ ├── Camera_Example.png +│ │ ├── Editor_Object_PostDraw.png +│ │ ├── Editor_Room_CreationOrder.png +│ │ ├── Editor_Room_RoomPhysics.png +│ │ ├── Editor_Room_RoomSettings.png +│ │ ├── Editor_Room_RoomViews.png +│ │ └── Icon_LMB.png +│ ├── Sounds.md +│ ├── Sprites.md +│ └── images +│ ├── Animation Curve +│ │ ├── Editor_Animation_Curves.png +│ │ ├── Editor_Animation_Curves_AddPoint.gif +│ │ ├── Editor_Animation_Curves_Buttons.gif +│ │ ├── Editor_Animation_Curves_ExpandList.png +│ │ ├── Editor_Animation_Curves_Moving.gif +│ │ ├── Editor_Animation_Curves_Range.png +│ │ ├── Editor_Animation_Curves_Zoom.gif +│ │ ├── Icon_Cmd.png +│ │ ├── Icon_Ctrl.png +│ │ ├── Icon_DeleteChannel.png +│ │ ├── Icon_LMB.png +│ │ ├── Icon_ToggleVisibility.png +│ │ ├── Icon_ZoomIn.png +│ │ ├── Icon_ZoomOut.png +│ │ └── image-20210222171930149.png +│ ├── Paths +│ │ ├── Editor_Paths.png +│ │ ├── Editor_Paths_Examples.png +│ │ ├── Editor_Paths_GridOptions.png +│ │ ├── Editor_Paths_Menu.png +│ │ ├── Icon_Cmd.png +│ │ ├── Icon_Ctrl.png +│ │ ├── Icon_Delete.png +│ │ ├── Icon_LMB.png +│ │ ├── Icon_RMB.png +│ │ └── Icon_ZoomReset.png +│ ├── Sounds +│ │ ├── Editor_Sound.png +│ │ └── Icon_OpenMixer.png +│ └── Sprites +│ ├── Editor_Sprites.png +│ ├── Editor_Sprites_ResizeProperties.png +│ ├── Icon_Cmd.png +│ ├── Icon_Ctrl.png +│ ├── Icon_Enter.png +│ ├── Icon_LMB.png +│ ├── Icon_MMB.png +│ ├── Icon_RMB.png +│ ├── Icon_Space.png +│ ├── Icon_SpriteSize.png +│ ├── Icon_Sprite_Loop.png +│ ├── Icon_Sprite_LoopOn.png +│ ├── Icon_Sprite_PingPong.png +│ ├── Icon_Sprite_Play.png +│ ├── Icon_ZoomReset.png +│ ├── Sprite_Broadcast_Message.gif +│ ├── Sprite_Loop_Markers.gif +│ ├── Sprite_RMB_Frame_menu.png +│ ├── Sprite_Stretch_Frame.gif +│ ├── basic_collisions_image.png +│ └── bbox_image.png +├── Git +│ ├── git push.md +│ ├── git remote.md +│ ├── git stash.md +│ ├── git tag.md +│ ├── git 使用目录.md +│ ├── git 分支.md +│ ├── git常用命令.md +│ ├── images +│ │ └── git常用命令 +│ │ ├── 164d0feb20bb887e +│ │ ├── 164d0feb478f6a3a +│ │ └── image-20201210193321666.png +│ └── 搭建git服务器.md +├── IDA +│ ├── IDA介绍.md +│ └── images +│ ├── 1575878402(1).jpg +│ ├── 1575879700(1).jpg +│ ├── 1575879832(1).jpg +│ ├── 1575880068(1).jpg +│ ├── 1575880278(1).jpg +│ ├── 1575882696(1).jpg +│ ├── 1575884276(1).jpg +│ ├── 1575884714(1).jpg +│ ├── 1575885029(1).jpg +│ ├── 1575885080(1).jpg +│ ├── 1575885157(1).jpg +│ ├── 1575885203(1).jpg +│ ├── 1575885287(1).jpg +│ ├── 1575885345(1).jpg +│ ├── 1575885580(1).jpg +│ ├── 1575886406(1).jpg +│ ├── 1575887173(1).jpg +│ ├── 1575888036(1).jpg +│ ├── 1575888105(1).jpg +│ ├── 1575888179(1).jpg +│ ├── 1575888667(1).jpg +│ ├── 1575956701(1).jpg +│ ├── 1575961028(1).jpg +│ ├── 1575961637(1).png +│ ├── 1575962952(1).jpg +│ ├── 1575964532(1).jpg +│ ├── 1575964633(1).jpg +│ ├── 1575964679(1).jpg +│ ├── 1575964929(1).jpg +│ ├── 1575969118(1).jpg +│ ├── 1575971681(1).jpg +│ ├── 1576058845(1).jpg +│ ├── IDA绘图.gif +│ ├── 代码转数据.gif +│ ├── 双击跳转.gif +│ └── 数据转代码.gif +├── LICENSE +├── LeetCode +│ ├── code +│ │ ├── 104. 二叉树的最大深度 +│ │ │ └── test +│ │ │ ├── test +│ │ │ │ ├── test.cpp +│ │ │ │ ├── test.vcxproj +│ │ │ │ └── test.vcxproj.filters +│ │ │ └── test.sln +│ │ ├── 125. 验证回文串 +│ │ │ └── test +│ │ │ ├── test +│ │ │ │ ├── test.cpp +│ │ │ │ ├── test.vcxproj +│ │ │ │ └── test.vcxproj.filters +│ │ │ └── test.sln +│ │ ├── 14. 最长公共前缀 +│ │ │ └── test +│ │ │ ├── test +│ │ │ │ ├── test.cpp +│ │ │ │ ├── test.vcxproj +│ │ │ │ └── test.vcxproj.filters +│ │ │ └── test.sln +│ │ ├── 141. 环形链表 +│ │ │ └── test +│ │ │ ├── test +│ │ │ │ ├── test.cpp +│ │ │ │ ├── test.vcxproj +│ │ │ │ └── test.vcxproj.filters +│ │ │ └── test.sln +│ │ ├── 19. 删除链表的倒数第N个节点 +│ │ │ └── test +│ │ │ ├── test +│ │ │ │ ├── test.cpp +│ │ │ │ ├── test.vcxproj +│ │ │ │ └── test.vcxproj.filters +│ │ │ └── test.sln +│ │ ├── 206. 反转链表 +│ │ │ └── test +│ │ │ ├── test +│ │ │ │ ├── test.cpp +│ │ │ │ ├── test.vcxproj +│ │ │ │ └── test.vcxproj.filters +│ │ │ └── test.sln +│ │ ├── 21. 合并两个有序链表 +│ │ │ └── test +│ │ │ ├── test +│ │ │ │ ├── test.cpp +│ │ │ │ ├── test.vcxproj +│ │ │ │ └── test.vcxproj.filters +│ │ │ └── test.sln +│ │ ├── 234. 回文链表 +│ │ │ └── test +│ │ │ ├── test +│ │ │ │ ├── test.cpp +│ │ │ │ ├── test.vcxproj +│ │ │ │ └── test.vcxproj.filters +│ │ │ └── test.sln +│ │ ├── 237. 删除链表中的节点 +│ │ │ └── test +│ │ │ ├── test +│ │ │ │ ├── test.cpp +│ │ │ │ ├── test.vcxproj +│ │ │ │ └── test.vcxproj.filters +│ │ │ └── test.sln +│ │ ├── 242. 有效的字母异位词 +│ │ │ └── test +│ │ │ ├── test +│ │ │ │ ├── test.cpp +│ │ │ │ ├── test.vcxproj +│ │ │ │ └── test.vcxproj.filters +│ │ │ └── test.sln +│ │ ├── 28. 实现strStr() +│ │ │ └── test +│ │ │ ├── test +│ │ │ │ ├── test.cpp +│ │ │ │ ├── test.vcxproj +│ │ │ │ └── test.vcxproj.filters +│ │ │ └── test.sln +│ │ ├── 283.移动零 +│ │ │ └── test +│ │ │ ├── test +│ │ │ │ ├── test.cpp +│ │ │ │ ├── test.vcxproj +│ │ │ │ └── test.vcxproj.filters +│ │ │ └── test.sln +│ │ ├── 344. 反转字符串 +│ │ │ └── test +│ │ │ ├── test +│ │ │ │ ├── test.cpp +│ │ │ │ ├── test.vcxproj +│ │ │ │ └── test.vcxproj.filters +│ │ │ └── test.sln +│ │ ├── 36. 有效的数独 +│ │ │ └── test +│ │ │ ├── test +│ │ │ │ ├── test.cpp +│ │ │ │ ├── test.vcxproj +│ │ │ │ └── test.vcxproj.filters +│ │ │ └── test.sln +│ │ ├── 38. 报数 +│ │ │ └── test +│ │ │ ├── test +│ │ │ │ ├── test.cpp +│ │ │ │ ├── test.vcxproj +│ │ │ │ └── test.vcxproj.filters +│ │ │ └── test.sln +│ │ ├── 387. 字符串中的第一个唯一字符 +│ │ │ └── test +│ │ │ ├── test +│ │ │ │ ├── test.cpp +│ │ │ │ ├── test.vcxproj +│ │ │ │ └── test.vcxproj.filters +│ │ │ └── test.sln +│ │ └── 8. 字符串转换整数 (atoi) +│ │ └── test +│ │ ├── test +│ │ │ ├── test.cpp +│ │ │ ├── test.vcxproj +│ │ │ └── test.vcxproj.filters +│ │ └── test.sln +│ ├── images +│ │ ├── 2-1FS0105529235.png +│ │ ├── 2-1FS01056261L.png +│ │ ├── 2-1FS0105A9146.png +│ │ ├── 2-1Q22620003J18.gif +│ │ ├── 2-1Q22H02935422.gif +│ │ ├── 2-1Q22R02H9133.gif +│ │ ├── 2-1Q22R035341H.gif +│ │ └── 2-1Q22R0360I09.gif +│ ├── 两数之和.md +│ ├── 加一.md +│ ├── 只出现一次的数字.md +│ ├── 广度优先搜索.md +│ ├── 时间复杂度.md +│ ├── 树.md +│ └── 算法概述.md +├── Linux +│ ├── Linux安装截图工具.md +│ ├── Linux工具 +│ │ ├── Radare2.md +│ │ └── images +│ │ └── Radare2 +│ │ ├── image-20201230155609153.png +│ │ ├── image-20201230155713077.png +│ │ └── image-20201230160101733.png +│ ├── Linux开发 +│ │ ├── AT&T内联汇编.md +│ │ ├── ELF文件格式.md +│ │ ├── LinuxPam模块.md +│ │ ├── Linux内核编译全记录.md +│ │ ├── Linux命令行参数处理.md +│ │ ├── Linux回收子进程.md +│ │ ├── Linux执行shell命令并获取输出.md +│ │ ├── Linux文件描述符.md +│ │ ├── Linux系统调用.md +│ │ ├── Linux进程开发.md +│ │ ├── Linux进程间通信.md +│ │ ├── Linux驱动模块的依赖.md +│ │ ├── Linux驱动调试.md +│ │ ├── Makefile.md +│ │ ├── Understanding_ELF.pdf +│ │ ├── code +│ │ │ └── fifo +│ │ │ ├── CMakeLists.txt +│ │ │ ├── fifo_read.cpp +│ │ │ └── fifo_write.cpp +│ │ ├── images +│ │ │ ├── 1583388103(1).jpg +│ │ │ ├── 1583388683(1).jpg +│ │ │ ├── 1583389138(1).jpg +│ │ │ ├── 1583392301(1).jpg +│ │ │ ├── 1583396226(1).jpg +│ │ │ ├── 1583396589(1).jpg +│ │ │ ├── 1583396860(1).jpg +│ │ │ ├── 1583397078(1).jpg +│ │ │ ├── 1583398656(1).jpg +│ │ │ ├── 1584632963(1).jpg +│ │ │ ├── 1584633211(1).png +│ │ │ ├── 1584633351(1).png +│ │ │ ├── 1584634467(1).jpg +│ │ │ ├── 1584634592(1).png +│ │ │ ├── 1584779604(1).png +│ │ │ ├── 1584779659.png +│ │ │ ├── LinuxPam模块 +│ │ │ │ ├── 907596-20180329194534174-1829700654.png +│ │ │ │ ├── image-20210611153222171.png +│ │ │ │ ├── image-20210611153412163.png +│ │ │ │ └── image-20210611172650862.png +│ │ │ ├── Linux内核编译全记录 +│ │ │ │ └── image-20200812115534546.png +│ │ │ ├── Linux回收子进程 +│ │ │ │ └── image-20200616171551843.png +│ │ │ ├── Linux文件描述符 +│ │ │ │ └── image-20200606172438112.png +│ │ │ ├── Linux进程开发 +│ │ │ │ ├── image-20200528151830099.png +│ │ │ │ ├── image-20200528164143353.png +│ │ │ │ └── image-20200528165826971.png +│ │ │ └── v2-8b728aaa77aefedc1a51797525486c17_720w.jpg +│ │ ├── kernel +│ │ │ ├── Linux内核反汇编.md +│ │ │ ├── Linux文件系统 +│ │ │ │ ├── dentry结构体.md +│ │ │ │ ├── images +│ │ │ │ │ ├── dentry结构体 +│ │ │ │ │ │ └── v2-b5d3e0b7b1f2a1aecb89ed0c93ac907c_720w.jpg +│ │ │ │ │ └── 索引节点对象 +│ │ │ │ │ ├── bg2011120405.png +│ │ │ │ │ └── image-20200814151919593.png +│ │ │ │ ├── open流程.eddx +│ │ │ │ ├── path结构体.md +│ │ │ │ ├── 创建文件.md +│ │ │ │ ├── 文件流程.eddx +│ │ │ │ ├── 索引节点对象.md +│ │ │ │ └── 超级块对象.md +│ │ │ ├── Linux文件系统.md +│ │ │ ├── Linux输入子系统 +│ │ │ │ ├── Linux输入子系统.md +│ │ │ │ └── images +│ │ │ │ └── Linux输入子系统 +│ │ │ │ ├── 14336242-0cc0ae43b7c25aef.png +│ │ │ │ ├── 14336242-c446ab163256b4e4.png +│ │ │ │ └── 14336242-dd6d91de64337029.png +│ │ │ ├── ecryptfs调试笔记.md +│ │ │ ├── ecryptfs重要函数.txt +│ │ │ ├── file_system_type.md +│ │ │ ├── images +│ │ │ │ ├── Linux内核反汇编 +│ │ │ │ │ └── image-20210305114603227.png +│ │ │ │ ├── Ubuntu内核编译 +│ │ │ │ │ ├── image-20200901121401806.png +│ │ │ │ │ ├── image-20200901121503588.png +│ │ │ │ │ ├── image-20200901125732043.png +│ │ │ │ │ ├── image-20200901130638749.png +│ │ │ │ │ ├── image-20200901130859919.png +│ │ │ │ │ ├── image-20200901131333515.png +│ │ │ │ │ ├── image-20200901131649911.png +│ │ │ │ │ ├── image-20200901131720707.png +│ │ │ │ │ ├── image-20200901131830926.png +│ │ │ │ │ ├── image-20200901132042392.png +│ │ │ │ │ └── image-20200901165952193.png +│ │ │ │ ├── gdb调试内核模块ko +│ │ │ │ │ ├── 33695fd212954f2aa4dcf513ac40d5f7.png +│ │ │ │ │ └── 57df302f6c58477bbc2854474bb94817.png +│ │ │ │ ├── kmem_cache +│ │ │ │ │ └── 4a471520078976.png +│ │ │ │ ├── 其他文件 +│ │ │ │ │ ├── image-20201127135217243.png +│ │ │ │ │ └── image-20201127135310932.png +│ │ │ │ ├── 内存管理 +│ │ │ │ │ └── 86f7099990ff736039089518e9000221.png +│ │ │ │ └── 内核调试所需 +│ │ │ │ ├── image-20201128145038211.png +│ │ │ │ ├── image-20201128145110076.png +│ │ │ │ ├── image-20201128150606347.png +│ │ │ │ ├── image-20201128150726724.png +│ │ │ │ ├── image-20201128150908892.png +│ │ │ │ ├── image-20201128151057937.png +│ │ │ │ ├── image-20201128151306867.png +│ │ │ │ ├── image-20201128151509817.png +│ │ │ │ ├── image-20201128151716357.png +│ │ │ │ ├── image-20201128152144204.png +│ │ │ │ └── image-20201128152621739.png +│ │ │ ├── kmem_cache.md +│ │ │ ├── match_token.md +│ │ │ ├── struct page.md +│ │ │ ├── task_struct.md +│ │ │ ├── write_begin_write_end.md +│ │ │ ├── 内存管理.md +│ │ │ ├── 内核调试 +│ │ │ │ ├── O0方式编译内核.md +│ │ │ │ ├── Thread overran stack.md +│ │ │ │ ├── Ubuntu内核编译.md +│ │ │ │ ├── gdb调试内核模块ko.md +│ │ │ │ ├── gdb调试命令.md +│ │ │ │ ├── gef-gdb增强插件.md +│ │ │ │ ├── images +│ │ │ │ │ ├── Thread overran stack +│ │ │ │ │ │ ├── image-20201210114625948.png +│ │ │ │ │ │ ├── image-20201210115543494.png +│ │ │ │ │ │ ├── image-20201210115720611.png +│ │ │ │ │ │ ├── image-20201210145615228.png +│ │ │ │ │ │ └── image-20201210145833637.png +│ │ │ │ │ ├── gdb调试命令 +│ │ │ │ │ │ ├── image-20201208164116488.png +│ │ │ │ │ │ ├── image-20201208164337983.png +│ │ │ │ │ │ ├── image-20201208165151595.png +│ │ │ │ │ │ └── image-20201208165828157.png +│ │ │ │ │ ├── gef-gdb增强插件 +│ │ │ │ │ │ ├── image-20201211175611650.png +│ │ │ │ │ │ ├── image-20201211183754742.png +│ │ │ │ │ │ └── image-20201217114218500.png +│ │ │ │ │ └── 内核崩溃调试 +│ │ │ │ │ ├── image-20201208100737934.png +│ │ │ │ │ ├── image-20201208102042986.png +│ │ │ │ │ └── image-20201208102445056.png +│ │ │ │ ├── 其他文件.md +│ │ │ │ ├── 内核崩溃调试.md +│ │ │ │ ├── 内核调试所需.md +│ │ │ │ └── 利用google syzkaller create-image.sh创建根文件系统.md +│ │ │ ├── 字符设备 +│ │ │ │ ├── Linux字符设备驱动.md +│ │ │ │ ├── images +│ │ │ │ │ └── 自动创建设备节点 +│ │ │ │ │ ├── image-20201208194854479.png +│ │ │ │ │ └── image-20201208194925120.png +│ │ │ │ └── 自动创建设备节点.md +│ │ │ ├── 开发环境搭建.md +│ │ │ └── 驱动签名.md +│ │ ├── manpage如何使用.md +│ │ ├── open系统调用.md +│ │ ├── setuid与setgid讲解.md +│ │ └── 中标麒麟解压vmlinuz.md +│ ├── Linux配置 +│ │ ├── A stop job.md +│ │ ├── Linux程序版本管理--update-alternatives.md +│ │ ├── Linux虚拟机移动后网卡配置.md +│ │ ├── images +│ │ │ └── Linux虚拟机移动后网卡配置 +│ │ │ └── wKiom1T-XYbi-EcsAAB9gymKFEg125.jpg +│ │ ├── linux代理.md +│ │ ├── linux压缩.md +│ │ ├── linux防火墙.md +│ │ ├── ubuntu18默认命令行模式.md +│ │ └── yum命令.md +│ ├── Shell +│ │ ├── Linux Shell.md +│ │ └── Linux重定向.md +│ ├── Ubuntu安装rime输入法.md +│ ├── images +│ │ ├── 1575944204(1).jpg +│ │ ├── 1575944294(1).jpg +│ │ ├── 1575950308(1).jpg +│ │ ├── 2019-11-17_00-37-06.png +│ │ ├── 2019-11-17_00-49-36.png +│ │ └── 2019-11-17_00-52-01.png +│ └── 网络开发 +│ ├── SIGPIPE信号.md +│ ├── TCPUDP端口0的作用.md +│ ├── codes +│ │ ├── noblock_epoll +│ │ │ ├── client.c +│ │ │ ├── makefile +│ │ │ └── server.c +│ │ └── tcp_udp_common_port +│ │ ├── CMakeLists.txt +│ │ └── main.cpp +│ ├── epoll.md +│ ├── gethostbyname.md +│ ├── images +│ │ ├── epoll +│ │ │ └── image-20200605133928894.png +│ │ ├── select +│ │ │ ├── image-20200601151745180.png +│ │ │ └── select2.png +│ │ ├── socket +│ │ │ ├── image-20200526133900745.png +│ │ │ ├── image-20200526135120907.png +│ │ │ └── image-20200526152658929.png +│ │ └── 半关闭 +│ │ └── image-20200601143218219.png +│ ├── poll.md +│ ├── select.md +│ ├── socket.md +│ ├── 半关闭.md +│ └── 端口复用.md +├── Office +│ └── Excel +│ ├── images +│ │ └── 添加下拉列表 +│ │ ├── image-20210118113451952.png +│ │ ├── image-20210118113542405.png +│ │ └── image-20210118113607797.png +│ └── 添加下拉列表.md +├── README.md +├── Scrapy +│ ├── Scrapy学习记录 +│ │ ├── Scrapy-001.md +│ │ └── Scrapy-002.md +│ └── XPath +│ └── XPath.md +├── TCPIP +│ ├── ARP协议.md +│ ├── IPV6.md +│ ├── TCPIP.md +│ ├── TCP四次握手断开连接.md +│ ├── TCP数据报结构以及三次握手.md +│ ├── TCP状态转换.md +│ ├── UDP协议.md +│ ├── UWP程序网络控制.md +│ ├── Windows网络编程 +│ │ ├── Client +│ │ │ ├── Client +│ │ │ │ ├── Client.cpp +│ │ │ │ ├── Client.vcxproj +│ │ │ │ ├── Client.vcxproj.filters +│ │ │ │ ├── pch.cpp +│ │ │ │ └── pch.h +│ │ │ └── Client.sln +│ │ ├── Server +│ │ │ ├── Server +│ │ │ │ ├── Server.cpp +│ │ │ │ ├── Server.vcxproj +│ │ │ │ ├── Server.vcxproj.filters +│ │ │ │ ├── pch.cpp +│ │ │ │ └── pch.h +│ │ │ └── Server.sln +│ │ ├── SocketTest.sln +│ │ ├── socket缓冲区以及阻塞模式.md +│ │ └── 使用listen()和accept()函数.md +│ ├── images +│ │ ├── 1-151020115S23R.jpg +│ │ ├── 1-151020132J32G.jpg +│ │ ├── 1-15102015224Wc.jpg +│ │ ├── ARP协议 +│ │ │ ├── v2-0f25c1953baf27d3770c4267ee5d5972_720w.jpg +│ │ │ ├── v2-1185c84e361ae6e5be13a78d917cec20_720w.jpg +│ │ │ ├── v2-12c0720dbf1143fb4ace0cb44398508f_720w.jpg +│ │ │ ├── v2-2216de823ce85a2cb629d2a0c774f9c3_720w.jpg +│ │ │ ├── v2-2485a31a69987ced88931c4a876bc4b3_720w.jpg +│ │ │ ├── v2-27582d8469224d8a28ee148cf3543e5e_720w.jpg +│ │ │ ├── v2-2788e3e061f7ee8af394dea99a5e732c_720w.jpg +│ │ │ ├── v2-317858c5badea87786011cb8eda6d3fd_720w.jpg +│ │ │ ├── v2-336746c18241ff5e5d8eba2b1ffac1da_720w.jpg +│ │ │ ├── v2-383f86e253b689d40ff20d648ce7afac_720w.jpg +│ │ │ ├── v2-412742914a0c84bb53a020e66a771ecb_720w.jpg +│ │ │ ├── v2-482e369fa6a8c4246af0164fbc69e2fc_720w.jpg +│ │ │ ├── v2-4be85864ff433d20b9e1165fe7cc36c3_720w.jpg +│ │ │ ├── v2-504474cb1210f7d4e72d718cd86148c8_720w.jpg +│ │ │ ├── v2-5e6176abaacf3839113a116891df9bde_720w.jpg +│ │ │ ├── v2-6522b0e3e1b7e7058152e70953428c74_720w.jpg +│ │ │ ├── v2-7f8bdda04b830562b73cf2b3d932f071_720w.jpg +│ │ │ ├── v2-816508f970cea1daf14f0610569c53db_720w.jpg +│ │ │ ├── v2-83365e93f2f60f5fb83772eca5dc8016_720w.jpg +│ │ │ ├── v2-af057558ef563955ede46f22ea77c335_720w.jpg +│ │ │ ├── v2-bade17b91e12c9f9ad5536858abf1350_720w.jpg +│ │ │ ├── v2-bc0c22f0043748b52cceaf185804694b_720w.jpg +│ │ │ ├── v2-c2b83eee5c96827c133c45a25d570c56_720w.jpg +│ │ │ ├── v2-d234cb3ec99e072f16c7000358284641_720w.jpg +│ │ │ └── v2-f898793fd3a4f37272dfc31d2ecbaa55_720w.jpg +│ │ ├── TCP状态转换 +│ │ │ └── image-20200529161621007.png +│ │ └── 正向代理及反射代理 +│ │ ├── 1fa9a9b31d854f0c1952b2d7c12ec3ce1603796814601.png +│ │ └── c207a95edcb805cee01db4f856b485fc1603796814601.png +│ ├── 本地回环.md +│ ├── 正向代理及反射代理.md +│ └── 网关.md +├── Vim +│ ├── Linux下的Vim配置.md +│ ├── Vim插件.md +│ ├── Vim管理多个文件.md +│ ├── Vim语法.md +│ ├── _vimrc +│ ├── images +│ │ ├── 2019-09-24180607.png +│ │ ├── gvim.png +│ │ └── 光标移动 +│ │ ├── v2-b071d0e288557777031d9c113d654986_720w.jpg +│ │ ├── v2-c37a245317ca494ce6122ac92a69a3bd_720w.jpg +│ │ └── v2-fe71b999b882db069dfb8714f9455b88_720w.jpg +│ ├── readme.txt +│ ├── vimrc +│ ├── vim插件之Leaderf.md +│ ├── vim查看和使用历史命令.md +│ ├── 光标移动.md +│ ├── 在ubuntu下安装最新vim.md +│ ├── 基于单词的移动.md +│ ├── 查找与替换substitute.md +│ └── 行的复制和移动.md +├── Windbg +│ ├── Windbg常用命令.md +│ ├── Windows XP调试环境.md +│ ├── devobj--drvobj.md +│ ├── images +│ │ ├── 1545125826(1).jpg +│ │ ├── 1545126200(1).png +│ │ ├── 1545126337(1).jpg +│ │ ├── 1559628616(1).jpg +│ │ ├── 1559628917(1).jpg +│ │ ├── 5712_4TBMWN5T83GFATE.png +│ │ └── Windbg常用命令 +│ │ ├── image-20200929174545996.png +│ │ ├── image-20200929182901320.png +│ │ └── image-20201009110236170.png +│ ├── patchguard.md +│ ├── scripts +│ │ └── MatchString.txt +│ ├── windbg命令关键字.md +│ ├── 伪寄存器.md +│ ├── 使用ip调试.md +│ ├── 内存操作.md +│ ├── 堆栈.md +│ ├── 搜索内存.md +│ ├── 支持Windows7 x64的kdusb3.md +│ ├── 数值表达式语法.md +│ ├── 有条件的执行命令--条件断点.md +│ ├── 运行script file.md +│ └── 进程 +│ └── windbg win32k.sys.md +├── Windows Driver Development +│ ├── 1.0-IRP堆栈探究.md +│ ├── 10.0-进程与线程.md +│ ├── 2.0缓存管理器.md +│ ├── 3.0MiniFilter的Work Item.md +│ ├── 4.0内存管理.md +│ ├── 5.0 FCB结构.md +│ ├── 5.1-CCB结构.md +│ ├── 6.0驱动读写方式.md +│ ├── 6.1-MDL直接方式读写.md +│ ├── 7.0-文件缓冲.md +│ ├── 8.0-中断请求级.md +│ ├── 9.0-文件系统的过滤.md +│ ├── DPC函数.md +│ ├── FsRtlEnterFileSystem.md +│ ├── IRP的同步和异步.md +│ ├── IoRegisterFsRegistrationChange.md +│ ├── IoSkipCurrentIrpStackLocation IoCopyCurrentIrpStackLocationToNext.md +│ ├── Lookaside结构.md +│ ├── MiniFilter通信.md +│ ├── MiniFilter驱动.md +│ ├── MiniFilter驱动隐藏文件夹的实现.md +│ ├── PNP.md +│ ├── StartIO.md +│ ├── WFP +│ │ ├── FWPS_INCOMING_VALUES0.md +│ │ ├── WFP.md +│ │ └── 分层标识.md +│ ├── Windows NT File System Internals +│ │ ├── README.txt +│ │ ├── filesys +│ │ │ ├── WS_FTP.LOG +│ │ │ ├── event +│ │ │ │ ├── MAKE-MC.BAT +│ │ │ │ ├── MAKEFILE +│ │ │ │ ├── README.TXT +│ │ │ │ ├── SOURCES +│ │ │ │ ├── dummy.c +│ │ │ │ ├── errmsg.msg +│ │ │ │ ├── errmsg.rc +│ │ │ │ └── sfsdevnt.def +│ │ │ ├── inc +│ │ │ │ ├── errmsg.h +│ │ │ │ ├── protos.h +│ │ │ │ ├── sfsd.h +│ │ │ │ └── struct.h +│ │ │ ├── readme1.txt +│ │ │ └── src +│ │ │ ├── cleanup.c +│ │ │ ├── close.c +│ │ │ ├── create.c +│ │ │ ├── devcntrl.c +│ │ │ ├── dircntrl.c +│ │ │ ├── fastio.c +│ │ │ ├── fileinfo.c +│ │ │ ├── flush.c +│ │ │ ├── makefile +│ │ │ ├── misc.c +│ │ │ ├── read.c +│ │ │ ├── sfsdinit.c +│ │ │ ├── shutdown.c +│ │ │ ├── sources +│ │ │ └── write.c +│ │ ├── filter-high +│ │ │ ├── WS_FTP.LOG +│ │ │ ├── inc +│ │ │ │ ├── protos.h +│ │ │ │ ├── sfilter.h +│ │ │ │ └── struct.h +│ │ │ ├── readme2.txt +│ │ │ ├── sfilter.ini +│ │ │ └── src +│ │ │ ├── attach.c +│ │ │ ├── close.c +│ │ │ ├── create.c +│ │ │ ├── dispatch.c +│ │ │ ├── fastio.c +│ │ │ ├── fsctrl.c +│ │ │ ├── makefile +│ │ │ ├── misc.c +│ │ │ ├── sfilinit.c +│ │ │ ├── sfilter.rc +│ │ │ └── sources +│ │ └── fsrec +│ │ ├── WS_FTP.LOG +│ │ ├── inc +│ │ │ └── sfsrec.h +│ │ ├── readme3.txt +│ │ └── src +│ │ ├── makefile +│ │ ├── sfsrec.c +│ │ └── sources +│ ├── Windows NT File System Internals - A Developer's Guide (1997).pdf +│ ├── Windows Security +│ │ ├── Anti Debug +│ │ │ └── 回溯调用栈.md +│ │ ├── GDT.md +│ │ ├── Hook +│ │ │ ├── IDT HOOK.md +│ │ │ ├── SSDT Hook.md +│ │ │ ├── i8042prt驱动.md +│ │ │ ├── images +│ │ │ │ ├── 1567581572(1).jpg +│ │ │ │ ├── 1567582661(1).jpg +│ │ │ │ ├── 1567582867(1).jpg +│ │ │ │ ├── 1567582931(1).jpg +│ │ │ │ └── 1567585530(1).jpg +│ │ │ └── 内核重载.md +│ │ ├── IDT.md +│ │ ├── Inject +│ │ │ └── APC注入.md +│ │ ├── KeUserModeCallback.md +│ │ ├── KernelCallbackTable.md +│ │ ├── PE +│ │ │ └── 输入表结构.md +│ │ ├── WRK内核编译调试环境搭建.md +│ │ ├── Windows驱动未公开秘籍.md +│ │ ├── images +│ │ │ ├── 1572507617(1).jpg +│ │ │ ├── 1572509079(1).jpg +│ │ │ ├── 1573556002(1).png +│ │ │ ├── 1573628447(1).jpg +│ │ │ ├── 1573630080(1).jpg +│ │ │ ├── 1576661590(1).jpg +│ │ │ └── KernelCallbackTable +│ │ │ ├── image-20200929162534322.png +│ │ │ ├── image-20200929162706499.png +│ │ │ ├── image-20200929162740527.png +│ │ │ ├── image-20200929162900653.png +│ │ │ ├── image-20200929162944726.png +│ │ │ └── image-20200929163010604.png +│ │ ├── ntoskrnl.md +│ │ ├── shadow SSDT获取.md +│ │ ├── 内核读写只读内存.md +│ │ ├── 禁用内核调试.md +│ │ ├── 系统调用.md +│ │ └── 系统调用(二).md +│ ├── images +│ │ ├── 11160817_1FOS.jpg +│ │ ├── 11160817_O21Y.jpg +│ │ ├── 11160817_puN0.jpg +│ │ ├── 1543485831(1).jpg +│ │ ├── 1544435336(1).jpg +│ │ ├── 1544436001(1).jpg +│ │ ├── 1545033751(1).jpg +│ │ ├── 1548818559(1).jpg +│ │ ├── 1556422665(1).jpg +│ │ ├── 1556433246(1).jpg +│ │ ├── 1560245421(1).jpg +│ │ ├── 1565934691(1).jpg +│ │ ├── 1568883056(1).jpg +│ │ ├── 1568886968(1).jpg +│ │ └── 1576813763(1).jpg +│ ├── sc.md +│ ├── windows禁用驱动签名.md +│ ├── wow64运行原理.md +│ ├── 内核sleep等待.md +│ ├── 内核模式下的字符操作.md +│ ├── 分层驱动.md +│ ├── 分层驱动IRP探究.md +│ ├── 取消IRP.md +│ ├── 字符串操作.md +│ ├── 完成例程.md +│ ├── 对象管理.md +│ ├── 文件过滤驱动 +│ │ └── 和老C一起学透明加解密.md +│ ├── 注册表保护.md +│ ├── 注册表操作.md +│ ├── 设备对象和符号链接.md +│ ├── 设备驱动框架.md +│ ├── 进程监控.md +│ ├── 驱动加载顺序.md +│ └── 驱动常用函数.md +├── php +│ ├── Composer教程.md +│ ├── Flarum分析.md +│ ├── images +│ │ ├── 1587521835(1).jpg +│ │ ├── 1587521909(1).jpg +│ │ ├── 1587521962(1).jpg +│ │ ├── 1587522010(1).jpg +│ │ ├── 1587522056(1).jpg +│ │ └── 1587522099(1).jpg +│ ├── php调试环境搭建.md +│ └── 使用PHPStudy和PHPStorm搭建调试环境.md +├── xposed +│ ├── BSTweaker使用方法.md +│ ├── Root BlueStacks.md +│ └── images +│ ├── 1561726620(1).jpg +│ ├── 1561726712(1).jpg +│ └── 1561726901(1).jpg +├── 密码学 +│ ├── images +│ │ ├── 分组密码 +│ │ │ ├── Cbc_decryption.png +│ │ │ ├── Cbc_encryption.png +│ │ │ ├── Ecb_decryption.png +│ │ │ ├── Ecb_encryption.png +│ │ │ ├── Tux.jpg +│ │ │ ├── Tux_ecb.jpg +│ │ │ └── Tux_secure.jpg +│ │ └── 数字签名 +│ │ ├── bg2011080901.png +│ │ ├── bg2011080902.png +│ │ ├── bg2011080903.png +│ │ ├── bg2011080904.png +│ │ ├── bg2011080905.png +│ │ ├── bg2011080906.png +│ │ ├── bg2011080907.png +│ │ ├── bg2011080908.png +│ │ ├── bg2011080909.png +│ │ ├── bg2011080910.png +│ │ ├── bg2011080911.png +│ │ ├── bg2011080912.png +│ │ ├── bg2011080913.png +│ │ ├── bg2011080914.jpg +│ │ ├── bg2011080915.png +│ │ ├── bg2011080916.png +│ │ ├── bg2011080917.png +│ │ ├── bg2011080918.png +│ │ ├── bg2011080919.jpg +│ │ └── bg2011080920.png +│ ├── 分组密码.md +│ ├── 对称加密算法.md +│ └── 数字签名.md +├── 未整理 +│ ├── Writing a File System Driver.md +│ └── vpb.txt +├── 科普 +│ ├── SSD为什么要4K对齐.md +│ └── images +│ └── SSD为什么要4K对齐 +│ ├── 4k-align-01.png +│ ├── 4k-align-02.png +│ ├── 4k-align-03.png +│ ├── 4k-align-04.png +│ ├── 4k-align-05.png +│ └── 4k-align-06.png +└── 逆向 + ├── Hex-Rays 十步杀一人,两步秒OLLVM-BCF.md + └── images + └── Hex-Rays 十步杀一人,两步秒OLLVM-BCF + ├── 715510_CWMGJQBEAHUPF2T.png + ├── 715510_QHFSTUY7P9K7ZZ8.png + ├── 715510_WKXR5ZGN36CEGQ4.png + └── 715510_ZEXU63SXCH3MQZD.png + +``` + +`ASM/C调用汇编.md`: + +```md +首先要有.MODEL伪指令 + +## .MODEL伪指令 + +语法如下: + +``` +.MODEL memorydel[,modeloptions] +``` + +例: + +``` +.MODEL flat stdcall +.MODEL flat c +``` + +其中的`C`和`STDCALL`是调用约定。 + +当使用`STDCALL`时,编译出来的函数名称类似为: + +``` +_name@n +``` + +如`AddTwo(int a, int b)`会编译出`_AddTwo@8`。 + + +如果是按照`C`的编译方式,则会编译出`_AddTwo`。 + +## C/C++调用汇编 + +在调用之前要先进行**函数声明**: + +在`.c`文件调用时: + +``` +extern int AddTwo(int a, int b); +``` + +在`.cpp`文件调用时: + +``` +extern "C" int AddTwo(int a, int b); +``` + +## 汇编调用C/C++函数 + +例: + +``` +.model flat, stdcall + +include aaa.inc +AddTwo proto C, a:dword, b:dword +printf proto C +``` + +STDCALL是和WinAPI兼容,但是它与C程序的调用规范不匹配。因此在声明由汇编模块调用的外部C或C++函数时,要给proto加上C的限定符。 + +如果在.model时声明和函数调用一致时,则不需要加限定符。 + +如: + +``` +.model flat, stdcall +include windows.inc +ReadFile proto, FileHandel:dword, lpBuffer:dword, numberToRead:dword, numberofRead:dword, lpOverlapped:dword +``` + + +``` + +`ASM/Linux下C和ASM混编.md`: + +```md +# Linux下C和ASM混编 + + + +### 前言 + +在某些情况下,我们可能会将C代码与汇编代码一起混合使用。比如,使用汇编代码直接与硬件进行交互,或者在处理任务时希望占用尽量少的资源同时获得最大的性能,而使用C代码处理一些更高级 的任务。通常情况下,混合使用C与汇编可分为以下三种情形: + +- 在C中调用汇编中定义的函数 +- 在汇编中调用C语言中的函数 +- 直接在C语言中嵌入汇编 + + + +在介绍C与汇编混合使用之前,先介绍一下在Linux系统中进行系统调用时传参的约定,以及在进行函数调用时的传参约定。 + + + +### Linux 系统调用约定 (这块可能不对) + +**系统调用**是用户程序与Linux 内核之间的接口,用于让内核执行一些系统任务,如文件访问、进程管理及网络任务等。在Linux中,有多种方式可以用于进行系统调用,这里只介绍通过使用`int $0x80`或`syscall`产生软中断来进行系统调用的方式。该方法比较简单直观,方便在汇编代码中进行系统调用。 + +#### int $0x80 + +在Linux x86 和Linux x86_64中,可以直接使用`int $0x80`命令来进行系统调用。以Linux x86为例,参数传递规则如下,其中返回值通过寄存器eax返回。 + +| 系统调用号 | 参数1 | 参数2 | 参数3 | 参数4 | 参数5 | 参数6 | 返回值 | +| ---------- | ----- | ----- | ----- | ----- | ----- | ----- | ------ | +| eax | ebx | ecx | edx | esi | edi | ebp | eax | + +系统调用号可以在`/usr/include/asm/unistd_32.h`文件中查看。在系统调用过程中,所有寄存器的值都会保持不变(除了`eax`用于返回值)。 + +> 由于在Linux x86_64上,寄存器的名称发生了变化,其参数传递规则见下面 + +#### syscall + +在Linux x86_64中引入了一条新的指令`syscall`,与`int $0x80`相比,由于不需要访问中断描述符表,所以会更快。其参数传递规则如下,其中返回值通过寄存器rax返回。 + +| 系统调用号 | 参数1 | 参数2 | 参数3 | 参数4 | 参数5 | 参数6 | 返回值 | +| ---------- | ----- | ----- | ----- | ----- | ----- | ----- | ------ | +| rax | rdi | rsi | rdx | r10 | r8 | r9 | rax | + +系统调用号可以在`/usr/include/asm/unistd_64.h`文件中查看。在系统调用过程中,会改变寄存器`rcx`和`r11`的内容,其他寄存器的内容会保持不变(除了`rax`用于返回值)。 + + + +### 函数调用传参约定 + +在Linux x86中,使用gcc编译器进行程序编译时,函数调用时的参数传递规则如下: + +- 函数参数通过栈传递,按照从右往左的顺序入栈; +- 函数返回值保存在寄存器`eax`中。 + +在Linux x86_64中,函数调用时的参数传递规则如下: + +- 前6个参数按从左往右的顺序分别通过寄存器`rdi`、`rsi`、`rdx`、`rcx`、`r8`、`r9`,剩下的参数按从右往左的顺序通过栈传递; +- 函数返回值保存在寄存器`rax`中。 + +> 函数调用时的参数传递规则实际上与**函数调用约定**有关,与编译器无关,常见的函数调用约定包括`c调用约定`、`std调用约定`、`x86 fastcall约定`以及`C++调用约定`等。gcc编译器采用的c调用约定。 + + + +### 在C中调用汇编中定义的函数 + +以Linux x86为例,用汇编语言编写一个hello_world函数,输出”Hello, World!\n”为例,其不需要任何参数,同时也没有返回值,相应的汇编代码如下: + +```assembly +.globl hello_world +.type hello_world, @function +.section .data +message: .ascii "Hello, World!\n" +length: .int . - message +.section .text +hello_world: + mov $4, %eax + mov $1, %ebx + mov $message, %ecx + mov length, %edx + int $0x80 + ret +``` + +> 由于使用gcc进行编译,因此汇编代码中使用AT&T语法。如果在用gcc编译时加上`-masm=intel`选项,则可以使用intel语法。当然,也可以使用nasm对汇编语言进行汇编,然后使用gcc完成链接过程,可参考[这里](https://www.devdungeon.com/content/how-mix-c-and-assembly)。 + +然后编写一个C程序调用该函数,如下: + +```c +extern void hello_world(); + +void main() +{ + hello_world(); +} +``` + +使用gcc进行编译,命令如下: + +```shell +gcc -m32 hello_world.c hello_world.s -o hello_world +``` + + + +下面通过参数传递将”Hello World!”传入到汇编代码中,修改如下: + +```assembly +.globl hello_world +.type hello_world, @function +.section .text +hello_world: + mov $4, %eax + mov $1, %ebx + mov 4(%esp), %ecx + mov $0xd, %edx + int $0x80 + ret +``` + +对应的C程序如下: + +```c +extern void hello_world(char* value); + +void main() +{ + hello_world("Hello World!\n"); +} +``` + + + +### 在汇编中调用C中的函数 + +以`printf`为例,通过在汇编代码中调用`printf()`函数,示例代码如下: + +```assembly +.extern printf +.globl main +.section .data +message: .ascii "hello,world!\n" +format: .ascii "%s" +.section .text +main: + push $message + push $format + mov $0, %eax + call printf + add $0x8, %esp + ret +``` + +使用gcc编译如下: + +```shell +gcc hello_world.s -o hello_world +``` + +> 1. 使用gcc编译汇编代码时,开始符号不再是_start而是main。由于main是一个函数,所以在最后必须要有`ret`指令; +> 2. 在调用函数之前,寄存器`eax`/`rax`的值必须设为0。 + + + +### 在C中嵌入汇编 + +最直接的方式是在C程序中嵌入汇编代码,以Linux x86_64为例,示例代码如下: + +```c +#include +int sum(int a, int b) +{ + asm("addl %edi, %esi"); + asm("movl %esi, %eax"); +} + +int main() +{ + printf("%d\n", sum(2, 3)); + return 0; +} +``` + +在上面的示例代码中,也可以将多条汇编指令写在一起,如下: + +```c +asm( + "addl %edi, %esi\n\r" + "movl %esi, %eax\n\r" + ); +``` + +**由于gcc编译器在进行解析时是先将汇编指令打印到一个文件中,所以需要带上格式化控制串。** + + + +### 扩展内联汇编 (Extended Asm) + +前面讨论的基本内联汇编只涉及到嵌入汇编指令,而在扩展形式中,我们还可以指定操作数,并且可以选择输入输出寄存器,以及指明要修改的寄存器列表。对于要访问的寄存器,并不一定要要显式指明,也可以留给GCC自己去选择,这可能让GCC更好去优化代码。扩展内联汇编格式如下: + +```c +asm ( assembler template + : output operands /* optional */ + : input operands /* optional */ + : list of clobbered registers /* optional */ +); +``` + + + + + +### 小结 + +对Linux平台下的系统调用及函数调用时的传参约定进行了介绍,同时简单介绍了C与汇编语言混合使用的三种情形。 + +> 如果想要进行更深入的理解,可自行查阅网上的相关资料。 + + + +### 相关链接 + +- [Mixing Assembly and C](https://abnerrjo.github.io/blog/2016/02/27/mixing-assembly-and-c/) +- [How to Mix C and Assembly](https://www.devdungeon.com/content/how-mix-c-and-assembly) +- [Linux System Calls](https://cs.lmu.edu/~ray/notes/syscalls/) +- [X86 Assembly/Interfacing with Linux](https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux) +``` + +`ASM/标志寄存器.md`: + +```md +## 标志寄存器 + +|标志位|名称|解释| +|--|--|--| +|CF|进位标志/Carry Flag|当运算结果的最高有效位有进位或借位时,CF=1,无符号数超出存储范围叫做进位| +|PF|奇偶标志位/Parity Flag|当运算结果字节中`1`的个数为零个或偶数个时,PF=1| +|AF|辅助进位标志/Auxiliary Carry Flag|算数操作结果的第三位(从0开始计数)如果产生了进位或者借位则将其置为1,否则置为0,常在BCD(binary-codedecimal)算术运算中被使用| +|ZF|零标志/Zero Flag|当运算结果为0,ZF=1| +|SF|符号标志/Sign Flag|当运算结果最高位为1时,SF=1| +|TF|跟踪标志/Trace Flag|当TF被设置为1时,CPU进入单步模式,所谓单步模式就是CPU在每执行一步指令后都产生一个单步中断。主要用于程序的调试。8086/8088中没有专门用来置位和清零TF的命令,需要用其他办法。| +|IF|中断标志/Interrupt Flag|决定CPU是否响应外部可屏蔽中断请求。IF为1时,CPU允许响应外部的可屏蔽中断请求| +|DF|方向标志/Direction Flag|决定串操作指令执行时有关指针寄存器调整方向。当DF为1时,串操作指令按递减方式改变有关存储器指针值,每次操作后使SI、DI递减。| +|OF|溢出标志/Overflow Flag|有符号数超出存储范围叫做溢出| + +[标志寄存器](https://web.archive.org/web/20101212091435/http://www.ltivs.ilc.edu.tw/kocp/mpu/m3/m3-1-3.htm) + + +``` + +`ASM/调用约定.md`: + +```md +# 函数调用约定 + +__cdecl __stdcall __fastcall + + +## __cdecl + +C语言调用约定,函数返回`ret`,函数体中包含`add esp,xxx`等指令,堆栈是由调用函数平衡的。可接收数量不定的参数,如printf + + +## __stdcall + +标准调用约定,函数返回`ret 0x10h`,参数从右至左传入堆栈,是由函数体自己平衡的。 + + +## __fastcall + +快速调用约定,函数返回`ret 0x10h`,参数最左边两个传入`ecx`、`edx`,其余参数从右至左传入堆栈,是由函数体自己平衡的。 + + + +https://bbs.pediy.com/thread-224583.htm +``` + +`C++/C++构造函数和析构函数.md`: + +```md +## 类的构造函数 + +### 默认的构造函数 + +类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行 + +构造函数的名称和类的名称是完全相同的,并且不会返回任何类型,也不会返回`void`。构造函数可用于为某些成员变量设置初始值。 + +``` +class Base +{ +public: + Base(); +}; + +Base::Base() +{ + printf("这里是Base类的构造函数\n"); +} + +int main() +{ + Base b; + return 0; +} +``` +上述代码执行时会打印构造函数里的语句。 + +### 带参数的构造函数 + +默认的构造函数没有任何参数,但如果需要,构造函数也可以带有参数。这样在创建对象时就会给对象赋初始值。 + +``` +class Base +{ +public: + Base(int x); + void PrintA(); +private: + int a; +}; + +Base::Base(int x) +{ + a = x; + printf("这里是Base类的构造函数\n"); +} + +void Base::PrintA() +{ + printf("a的值为%d\n", a); +} + +int main() +{ + Base b(5); + b.PrintA(); + return 0; +} +``` + +### 使用初始化列表来初始化字段 + +``` +class Base +{ +public: + Base(int x, int y); + void PrintA(); +private: + int a; + int b; +}; + +Base::Base(int x, int y):a(x),b(y) +{ + // 上面的初始化列表和 a = x; b = y; 等同 + printf("这里是Base类的构造函数\n"); +} + +void Base::PrintA() +{ + printf("a=%d,b=%d\n", a,b); +} +``` + +## 析构函数 + +``` +class Base +{ +public: + Base(int x, int y); + void PrintA(); + ~Base() + { + printf("这里是Base类的析构函数\n"); + } +private: + int a; + int b; +}; +``` +类的析构函数会在每次删除所创建的对象时执行。 +``` + +`C++/C++虚函数.md`: + +```md + +在编译器中打开内存布局: + +C/C++ -> 命令行 在其他选项上填写 `/d1 reportAllClassLayout` + + +C++的虚函数主要作用是“运行时多态”,父类中提供虚函数的实现,为子类提供默认的函数实现。 + +子类可以重写父类的虚函数实现子类的特殊化。 + +例: +``` +class A +{ + int a; + int b; +public: + virtual void Say() + { + printf("A Say\n"); + } + virtual void Say2() + { + printf("A Say2\n"); + } + + void Say3() + { + printf("A Say3\n"); + } +}; + +class B : public A +{ + int c; +public: + void Say() + { + printf("B Say\n"); + } + void Say3() + { + printf("B Say3\n"); + } +}; + +class C : public A +{ + int d; +public: + virtual void Say() + { + printf("C Say\n"); + } +}; + +class D : public B, public C +{ + int e; +public: + virtual void Say() + { + printf("D Say\n"); + } +}; +``` + +内存布局如下: + +``` +class A size(12): + +--- + 0 | {vfptr} + 4 | a + 8 | b + +--- + +A::$vftable@: + | &A_meta + | 0 + 0 | &A::Say + 1 | &A::Say2 + +A::Say this adjustor: 0 +A::Say2 this adjustor: 0 +``` + +先看类A,虚表指针放在第一位(类内偏移从0开始),后面依次是成员变量,成员变量依据声明的顺序进行排列,成员函数不占内存空间。 + +紧接着是虚表在内存中的分布,下面列出了虚函数,左侧的0和1是这个虚函数的序号,如果还有更多的虚函数,会依次列出来。 + +编译器是在构造函数中创建这个虚表指针和虚表的。 + +那么编译器是如何利用虚表指针与虚表来实现多态的呢?是这样的,**当创建一个含有虚函数的父类的对象时,编译器在对象构造时将虚表指针指向父类的虚函数;同样,当创建子类的对象时,编译器在构造函数里将虚表指针(子类只有一个虚表指针,它来自父类)指向子类的虚表(这个虚表里面的虚函数入口地址是子类的)。** + +所以,如果是调用`A *a = new B();`,生成的是子类的对象,在构造时,子类对象的虚指针指向的是子类的虚表,接着由`B*`到`A*`的转换并没有改变虚表指针,所以这时候`a->Say()`实际上是`b->Say()`,它在构造的时候就已经指向了子类的`Say`,所以调用的是子类的虚函数,这就是多态了。 + +``` +class B size(16): + +--- + 0 | +--- (base class A) + 0 | | {vfptr} + 4 | | a + 8 | | b + | +--- +12 | c + +--- + +B::$vftable@: + | &B_meta + | 0 + 0 | &B::Say + 1 | &A::Say2 + +B::Say this adjustor: 0 +``` + +看类B,可以看到,虚表指针被继承了,且仍位于内存排布的起始处,下面是父类的成员变量a和b,接下来是子类的成员变量c,注意虚表指针只有一个,子类并没有再生成虚表指针了。 + +接下来看一下虚表,我们重写了父类的`Say`虚函数,虚表的0号是子类的`Say`,而1号是从父类继承下来的`Say2`。也就是说,如果定义了B的对象,那么在构造是,虚表指针就会指向这个虚表,以后如果是调用Say,那么会从子类中寻找对应的虚函数,如果是调用Say2,那么会从父类中寻找对应的虚函数。 + +下面看一下以下代码的运行结果: + +``` +int main() +{ + B * pb = (B*)new A(); // 虚表指针指向A的虚表 + pb->Say(); // A Say + pb->Say2(); // A Say2 + pb->Say3(); // Say3是普通函数,经过转换,Say3其实是B类的函数 + + A * pa = (A*)new B(); + pa->Say(); + pa->Say2(); + pa->Say3(); +} +``` + +多重继承内存布局: + +``` +class D size(36): + +--- + 0 | +--- (base class B) + 0 | | +--- (base class A) + 0 | | | {vfptr} + 4 | | | a + 8 | | | b + | | +--- +12 | | c + | +--- +16 | +--- (base class C) +16 | | +--- (base class A) +16 | | | {vfptr} +20 | | | a +24 | | | b + | | +--- +28 | | d + | +--- +32 | e + +--- + +D::$vftable@B@: + | &D_meta + | 0 + 0 | &D::Say + 1 | &A::Say2 + +D::$vftable@C@: + | -16 + 0 | &thunk: this-=16; goto D::Say + 1 | &A::Say2 + +D::Say this adjustor: 0 +``` + +多重继承中含有多个虚表指针,此示例中包含两个虚表指针。 + +本文暂不讨论虚继承。 +``` + +`C++/Resharp快捷键汇总.md`: + +```md +# Resharp快捷键汇总 + + +## 常用的快捷键 + +Ctrl + N search everywhere + +Ctrl + B 跳到变量申明处 + +Ctrl + Alt + F 格式化代码 + +Alt + F7 查找变量,方法的引用 + +Ctrl + Shift + F4 关闭查找结果tab + + + +## 编辑 + +Ctrl + Space 代码完成 + +Ctrl + Shift + Space代码完成 + +Ctrl + Alt + Space代码完成 + +Ctrl + P 显示参数信息 + +Alt + Insert 生成构造函数,属性,重载成员函数,实现接口 + +Ctrl + Alt + J 生成if,try..catch,#region块 + +Ctrl + / 注释及取消//注释 + +Ctrl + Shift + / 注释及取消/**/注释 + +Ctrl + W 增量选择块 + +Ctrl + Shift + W 增量反选 + +Alt + Enter 显示错误的修正方法 + +Ctrl + Alt + F 格式化代码 + +Ctrl + Alt + O 根据指示修改优化 + +Ctrl + D 复制当前行或选定的块 + +## 查找 + +Alt + F7 查找变量,方法的引用 + +Alt + Shift + F7 在设定范围中查找 + +Ctrl + Shift + F7 高亮显示 + +Ctrl + Alt + Up / Down 定位到下/前一个引用 + +Ctrl + Alt + F7 快速定位引用位置 + +Ctrl + Shift + F4 关闭查找结果tab + +Ctrl + Alt + U 定位到查找结果 + +## 导航 + +Ctrl + N 定位到类型 + +Ctrl + Shift + N根据文件名定位 + +Ctrl + E 最近文件列表 + +Ctrl + Shift +Backspace 上次编辑位置 + +Ctrl + B 跳到变量申明处 + +Ctrl + Alt + B 跳到继承类或接口处 + +Ctrl + U 调到基类 + +Ctrl + Shift + T 跳到类型申明处 + +Alt + Up / Down跳到下/上一个方法 + +Ctrl + F12 + +F2 / Shift + F2 下/上一个高亮度显示的错误 + +Ctrl + Shift + E Stack Trace + +## Refactoring + +F6 将类移入另外的命名空间 + +Shift + F6 方法更名 + +Ctrl + F6 更改签名 + +Ctrl + Alt + N + +Ctrl + Alt + M 将选定块抽取为一个方法 + +Ctrl + Alt + V 更改变量名称和类型 + +## 模板 + +Tab 根据缩写生成模板 + +Ctrl + J 插入模板 + +内置模板: + +asrt 生成assertion + +ear生成空数组 + +foreach 生成foreach 块 + +ital生成ArrayList的迭代循环 + +itar 生成array的迭代循环 + +itdic 生成dictionary的迭代循环 + +loop生成loop循环 + +out 打印串 + +outv 打印变量值 + +pci public const int + +pcs public const string + +pro protected + +psr public static readonly + +psvm main() method declaration + +ritar Iterate an array in reverse order + +sfc Safely cast variable + +thr throw new + +toar 将ArrayList 转化为array + + +``` + +`C++/函数名是函数地址吗.md`: + +```md +函数名并不是函数地址的代表,这种误解与数组名就是指针一样犯了相同的错误。函数名是函数实体的代表,不是地址的代表,当然,你马上就会有疑问,平时我们不都是把函数名作为函数的地址吗?是的,我可以告诉你,函数名可以作为函数的地址,但是,绝大多数人都忽略了一个条件,从函数到指针的隐式转换是函数名在表达式中的行为,就是说,这个转换仅在表达式中才会发生,这仅是函数名众多性质中的一个,而非本质,函数名的本质就是函数实体的代表。 + +到了C++,由于C++规定,非静态成员函数的左值不可获得,因此非静态成员函数不存在隐式左值转换,即不存在像常规函数那样的从函数到指针的隐式转换,所以必须在非静态成员函数前使用&操作符才能获得地址。 +``` + +`C++/函数模板.md`: + +```md +## 函数模板 + + + +如果许多函数在主体逻辑一致,但参数类型不同,此时需要函数模板。 + + + +其实也可以写函数重载,但需要的类型多的时候,就需要写多个函数,此时程序员的重复工作量是很大的,唯独方便了调用函数的人。 + + + +还有写函数重载,当函数的主体逻辑需要改变时,就需要修改所有的重载函数,此时可能会丢、或忘记修改某个函数,造成错误。所以使用函数模板就更加方便。 + + + +![image-20200520182738355](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200520182738355.png) + + + + +``` + +`C++/类模板.md`: + +```md +# 类模板 + + + +## 语法 + + + +![image-20200520183250293](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200520183250293.png) +``` + +`C++/重载.md`: + +```md +## 定义 + +对于重载函数来说,它们应该在**形参数量或形参类型**上有所不同。 + +**不允许两个函数除了返回类型外其他所有的要素都相同** + +下面的例子是错误的: + +```C++ +Record lookup(const Account&) +bool lookup(const Account&) +``` + +## 运算符重载 + +```C++ +bool operator+(Account A, Account B); +``` + +``` + +`CMake/CMake.md`: + +```md +# CMake + +你或许听过好几种 Make 工具,例如 GNU Make ,QT 的 qmake ,微软的 MS nmake,BSD Make(pmake),Makepp,等等。这些 Make 工具遵循着不同的规范和标准,所执行的 Makefile 格式也千差万别。这样就带来了一个严峻的问题:如果软件想跨平台,必须要保证能够在不同平台编译。而如果使用上面的 Make 工具,就得为每一种标准写一次 Makefile ,这将是一件让人抓狂的工作。 + +**CMake 就是针对上面问题所设计的工具:它首先允许开发者编写一种平台无关的 CMakeList.txt 文件来定制整个编译流程,然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。从而做到“Write once, run everywhere”。** + + + +适用于项目 + +自动生成所用的编译脚本 + + + +最简单的项目只需要三行`CMakeLists.txt`文件。 + + + +```shell +cmake_minimum_required(VERSION 3.10) + +# set the project name +project(Tutorial) + +# add the executable +add_executable(Tutorial tutorial.cxx) +``` + + + + +``` + +`CMake/makefile.md`: + +```md +# makefile + +## makefile的规则 + +``` +target ... : prerequisites ... + command + ... + ... +``` + +`target`: 可以是一个object file,也可以是一个可执行文件,还可以是一个标签(label)。 +`prerequisites`: 生成该`target`所依赖的文件 +`command`: 该`target`要执行的命令(任意的shell命令) + +> `prerequisites`中如果有一个以上的文件比`target`文件要新的话,`command`所定义的命令就会被执行。 + +以上就是makefile最核心的内容。 + + +## 一个示例 + +``` +edit : main.o kbd.o command.o display.o \ + insert.o search.o files.o utils.o + cc -o edit main.o kbd.o command.o display.o \ + insert.o search.o files.o utils.o + +main.o : main.c defs.h + cc -c main.c +kbd.o : kbd.c defs.h command.h + cc -c kbd.c +command.o : command.c defs.h command.h + cc -c command.c +display.o : display.c defs.h buffer.h + cc -c display.c +insert.o : insert.c defs.h buffer.h + cc -c insert.c +search.o : search.c defs.h buffer.h + cc -c search.c +files.o : files.c defs.h buffer.h command.h + cc -c files.c +utils.o : utils.c defs.h + cc -c utils.c +clean : + rm edit main.o kbd.o command.o display.o \ + insert.o search.o files.o utils.o +``` + +反斜杠(`\`)是换行符的意思。这样比较便于`makefile`的阅读。我们可以把这个内容保存在名字 为“makefile”或“Makefile”的文件中,然后在该目录下直接输入命令 `make` 就可以生成执行文件edit。如果要删除执行文件和所有的中间目标文件,那么,只要简单地执行一下 `make clean` 就 可以了。 + +在这个makefile中,目标文件(target)包含:执行文件edit和中间目标文件( *.o ),依赖文 件(prerequisites)就是冒号后面的那些 .c 文件和 .h 文件。每一个 .o 文件都有 一组依赖文件,而这些 .o 文件又是执行文件 edit 的依赖文件。依赖关系的实质就是说明了目 标文件是由哪些文件生成的,换言之,目标文件是哪些文件更新的。 + +在定义好依赖关系后,后续的那一行定义了如何生成目标文件的操作系统命令,一定要以一个 `Tab` 键作为开头。 + + +## make是如何工作的 + +在默认的方式下,也就是我们只输入`make`命令。那么: + +1. make会在当前目录下找名字叫`makefile`的文件 +2. 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中为`edit`,并把这个文件作为最终的目标文件 +3. 如果`edit`不存在,则找`edit`所依赖的`.o`文件 +4. 如果`.o`文件也不存在,则找`.o`所依赖的C文件和H文件 +5. 最终make生成`.o`,然后再用`.o`生成`edit` + + +## clean + +像`clean`这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过我们可以显示地要make执行,即命令`make clean`,以此来清除所有的目标文件,以便重新编译。 + + +## makefile中使用变量 + +在上面的例子中,先让我们看看edit的规则: + +``` +edit : main.o kbd.o command.o display.o \ + insert.o search.o files.o utils.o + cc -o edit main.o kbd.o command.o display.o \ + insert.o search.o files.o utils.o +``` + +可以看到`.o`文件的字符串被重复了两次,如果我们的工程中需要再加入一个`.o`文件,那么我们需要在两个地方加(应该是三个地方,还有一个在clean中)。当前的makefile文件并不复杂,所以我们可以在这几个地方都加上。但是如果makefile变得复杂,我们就可能会忘掉一个需要加入的地方,而导致编译失败。所以,为了makefile的易维护,在makefile中我们可以使用变量。 + +比如,我们声明一个变量,叫`objects`,`OBJECTS`, `objs`, ` OBJS`,不管什么,只要能够表示obj文件就行了。我们在makefile一开始就这样定义: + +``` +objects = main.o kbd.o command.o display.o \ + insert.o search.o files.o utils.o +``` + +于是,我们就可以很方便地在我们的makefile中以`$(objects)`的方式来使用这个变量,于是改良的makefile就变成下面的这个样子: + +``` +objects = main.o kbd.o command.o display.o \ + insert.o search.o files.o utils.o + +edit : $(objects) + cc -o edit $(objects) +main.o : main.c defs.h + cc -c main.c +kbd.o : kbd.c defs.h command.h + cc -c kbd.c +command.o : command.c defs.h command.h + cc -c command.c +display.o : display.c defs.h buffer.h + cc -c display.c +insert.o : insert.c defs.h buffer.h + cc -c insert.c +search.o : search.c defs.h buffer.h + cc -c search.c +files.o : files.c defs.h buffer.h command.h + cc -c files.c +utils.o : utils.c defs.h + cc -c utils.c +clean : + rm edit $(objects) +``` + + +## 让makefile自动推导 + +GNU的make很强大,它**可以自动推导文件以及文件依赖关系后面的命令**,于是我们就没必要去在每一个`.o`文件后都写上类似的命令,因为,我们的make会自动识别,并自己推导命令。 + +**只要make看到一个`.o`文件,它就会自动的把`.c`文件加在依赖关系中,如果make找到一个`whatever.o`,那么`whatever.c`就会是`whatever.o`的依赖文件。并且`cc -c whatever.c`也会被推导出来。**于是,新的makefile如下: + +``` +objects = main.o kbd.o command.o display.o \ + insert.o search.o files.o utils.o + +edit : $(objects) + cc -o edit $(objects) + +main.o : defs.h +kbd.o : defs.h command.h +command.o : defs.h command.h +display.o : defs.h buffer.h +insert.o : defs.h buffer.h +search.o : defs.h buffer.h +files.o : defs.h buffer.h command.h +utils.o : defs.h + +.PHONY : clean +clean : + rm edit $(objects) +``` + +这种方法,也就是make的`隐晦规则`。在上面文件内容中,`.PHONY`表示`clean`是个伪目标文件。 + +## 另类风格的makefile + +上面的makefile中看到有多个头文件被多次重复,也可以将它们全部收拢起来: + +``` +objects = main.o kbd.o command.o display.o \ + insert.o search.o files.o utils.o + +edit : $(objects) + cc -o edit $(objects) + +$(objects) : defs.h +kbd.o command.o files.o : command.h +display.o insert.o search.o files.o : buffer.h + +.PHONY : clean +clean : + rm edit $(objects) +``` + +缺点:虽然这样的makefile变得很简单,但我们的文件依赖关系就显得有点凌乱了,如果文件一多,要加入几个新的`.o`文件,那就理不清了。 + + +## 清空目标文件的规则 + +每个makefile中都应该写一个清空目标文件的规则(虽然不是必要的),这不仅便于重编译,也很利于保持文件的清洁。 + +一般的风格: +``` +clean: + rm edit $(objects) +``` + +更为稳健的做法: + +``` +.PHONY : clean +clean : + -rm edit $(objects) +``` + +前面说过,`.PHONY`表示`clean`是一个`伪目标`。而在`rm`命令前加一个`-`的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。当然,`clean`的规则不要放在文件的开头,不然,这就变成make的默认目标。不成文的规矩是--“clean从来都放在文件的最后”。 +``` + +`CMake/编译ollvm.md`: + +```md +## 安装环境 + +CMake + +VS2019 + +Mingw + +bool bRet = false; +do +{ + HCRYPTPROV Rnd; + LPCSTR UserName = "MyKeyContainer"; + if (CryptAcquireContextA(&Rnd, UserName, NULL, PROV_RSA_FULL, 0)) + { + + } + else + { + break; + } + + if (CryptGenRandom(Rnd, 16, (BYTE*)key)) + { + bRet = true; + } + else + { + break; + } + + if (CryptReleaseContext(Rnd, 0)) + { + + } + else + { + break; + } +} while (false); +if (bRet) +{ + memset(ctr, 0, 16); + + // Once the seed is there, we compute the + // AES128 key-schedule + aes_compute_ks(ks, key); + + seeded = true; +} +else +{ + errs() << "Windows CryptGenRandom Error \n"; +} + +``` + +`Docker/Docker安装.md`: + +```md +# Docker安装 + + + +## 环境 + +Debian 9 + + + +## 官方安装脚本 + +```shell +curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun +``` + + + +## 安装 Docker Engine-Community + +在首次安装Docker Engine-community之前,需要设置Docker仓库(也就是源地址?)。 + + + +### 设置仓库 + +安装 apt 依赖包,用于通过 HTTPS 来获取仓库。 + +```shell +apt-get update + +apt-get install \ + apt-transport-https \ + ca-certificates \ + curl \ + gnupg2 \ + software-properties-common + +``` + + + +添加Docker的官方GPG密钥: + +```shell +curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add - +``` + + + +查看是否正确安装上GPG密钥: + +```shell +root@debian /h/superman# apt-key finger +/etc/apt/trusted.gpg +-------------------- +pub rsa4096 2017-02-22 [SCEA] + 9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88 +uid [ unknown] Docker Release (CE deb) +sub rsa4096 2017-02-22 [S] + +``` + + + +**设置稳定版仓库:** + +```shell +add-apt-repository \ + "deb [arch=amd64] https://download.docker.com/linux/debian \ + $(lsb_release -cs) \ + stable" +``` + + + +## 安装Docker Engine-Community + +```shell +apt-get update +apt-get install docker-ce docker-ce-cli containerd.io +``` + + + +## 验证是否正确安装 + +```shell +root@debian:/home/superman# docker run hello-world +Unable to find image 'hello-world:latest' locally +latest: Pulling from library/hello-world +0e03bdcc26d7: Pull complete +Digest: sha256:d58e752213a51785838f9eed2b7a498ffa1cb3aa7f946dda11af39286c3db9a9 +Status: Downloaded newer image for hello-world:latest + +Hello from Docker! +This message shows that your installation appears to be working correctly. + +To generate this message, Docker took the following steps: + 1. The Docker client contacted the Docker daemon. + 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. + (amd64) + 3. The Docker daemon created a new container from that image which runs the + executable that produces the output you are currently reading. + 4. The Docker daemon streamed that output to the Docker client, which sent it + to your terminal. + +To try something more ambitious, you can run an Ubuntu container with: + $ docker run -it ubuntu bash + +Share images, automate workflows, and more with a free Docker ID: + https://hub.docker.com/ + +For more examples and ideas, visit: + https://docs.docker.com/get-started/ + +``` + + +``` + +`GO/go语言静态库的编译和使用.md`: + +```md +# go语言静态库的编译和使用 + +本文主要介绍go语言静态库的编译和使用方法,以windows平台为例,linux平台步骤一样,具体环境如下: + +```shell +>echo %GOPATH% +E:\share\git\go_practice\ + +>echo %GOROOT% +C:\Go\ + +>tree /F %GOPATH%\src +卷 work 的文件夹 PATH 列表 +卷序列号为 0009-D8C8 +E:\SHARE\GIT\GO_PRACTICE\SRC +│ main.go +│ +└─demo + demo.go +``` + +在%GOPATH%\src目录,有demo包和使用demo包的应用程序main.go,main.go代码如下: + +```go +package main + +import "demo" + +func main() { + demo.Demo() +} +``` + +demo包中的demo.go代码如下: + +```go +package demo + +import "fmt" + +func Demo() { + fmt.Println("call demo ...") +} +``` + +由于demo.go是在%GOPATH%\src目录下的一个包,main.go在import该包后,可以直接使用,运行main.go: + +```shell +>go run main.go +call demo ... +``` + + + +现在,需要将demo.go编译成静态库demo.a,不提供demo.go的源代码,让main.go也能正常编译运行,详细步骤如下: + +### 编译静态库demo.a + +```shell +>go install demo +``` + +在命令行运行go install demo命令,会在%GOPATH%目录下生相应的静态库文件demo.a(windows平台一般在%GOPATH%\src\pkg\windows_amd64目录)。 + +### 编译main.go + +进入main.go所在目录,编译main.go: + +```shell +>go tool compile -I E:\share\git\go_practice\pkg\windows_amd64 main.go +``` + +`-I`**选项指定了demo包的安装路径**,供main.go导入使用,即E:\share\git\go_practice\pkg\win +dows_amd64目录,编译成功后会生成相应的目标文件main.o。 + + + +### 链接main.o + +```shell +>go tool link -o main.exe -L E:\share\git\go_practice\pkg\windows_amd64 main.o +``` + +`-L`选项**指定了静态库demo.a的路径**,即E:\share\git\go_practice\pkg\win +dows_amd64目录,链接成功后会生成相应的可执行文件main.exe。 + + + +### 运行main.exe + +```shell +>main.exe +call demo ... +``` + + + +现在,就算把demo目录删除,再次编译链接main.go,也能正确生成main.exe: + +```shell +>go tool compile -I E:\share\git\go_practice\pkg\windows_amd64 main.go + +>go tool link -o main.exe -L E:\share\git\go_practice\pkg\windows_amd64 main.o + +>main.exe +call demo ... +``` + +但是,如果删除了静态库demo.a,就不能编译main.go,如下: + +```shell +>go tool compile -I E:\share\git\go_practice\pkg\windows_amd64 main.go +main.go:3: can't find import: "demo" +``` + +------ + + + + + +# 发布方案 + +将要发布的闭源包.a文件放到 $GOROOT/pkg/$GOOS_$GOARCH/ 目录中. + +再将发布包的源码保留只有包声明的源文件放到 $GOROOT/src/pkg/ 目录中. + +操作如下: + +## 发布方: + +例如有say包要发布: + +``` +$cd $GOPATH/src/say +$cat say.go +// say something package +package say + +import "fmt" + +// private function +func say(str string){ + fmt.Println(str) +} + +// Say hi +func Hi(){ + say("Hi......") +} + +// Say hello to someone +func Hello(me string){ + say("Hello" + me) +} +``` + +首先编译生成say包的.a文件(如果要发布到多种系统架构,需要修改编译参数交叉编译出多种发布文件) + +``` +$go install +$ls $GOPATH/pkg/$GOOS_$GOARCH/say.a +``` + +其次修改发布包对应的源文件(两种方式任选) + +最简单的修改方式 + +``` +$echo 'package say' > say.go +``` + +保留导出的接口与API注释供查看使用 + +``` +$cat > say.go <注:这个第三方的路径要和编译时的包路径一致 + +如果三方包保留了接口与API注释, 还可以直接使用godoc命令查看 + +```go +$godoc say +PACKAGE DOCUMENTATION + +package say + import "say" + + say something package + +FUNCTIONS + +func Hi() + Say hi + +func Hello(me string) + Say hello to someone +``` + + + +## Makefile 示例 + +```makefile +# 在这里添加所有的gofile +gofiles = main.go file2.go + +# 这里是生成的二进制文件名 +target_name = "main" + +# 这里是pkg/platform路径 +pkg_path = "/root/go/pkg/linux_amd64" + +target : + go tool compile -o temp.o -I ${pkg_path} ${gofiles} + go tool link -o ${target_name} -L ${pkg_path} temp.o +``` + + +``` + +`GO/命令源码文件.md`: + +```md +# 命令源码文件 + +命令源码文件是程序的运行入口,是每个可独立运行的程序必须拥有的。我们可以通过构建或安装,生成与其对应的可执行文件,后者一般会与该命令源码文件的直接父目录同名。 + + + +**如果一个源码文件声明属于main包,并且包含一个无参数声明且无结果声明的main函数,那么它就是命令源码文件。** + + + +```go +package main +import "fmt" +func main() { + fmt.Println("Hello, world!") +} +``` + + + + + +## 命令源码文件怎样接收参数 + +我们先看一段不完整的代码: + +```go + +package main + +import ( + // 需在此处添加代码。[1] + "fmt" +) + +var name string + +func init() { + // 需在此处添加代码。[2] +} + +func main() { + // 需在此处添加代码。[3] + fmt.Printf("Hello, %s!\n", name) +} + +``` + +**Go 语言标准库中有一个代码包专门用于接收和解析命令参数。这个代码包的名字叫`flag`。** + +```go +flag.StringVar(&name, "name", "everyone", "The greeting object.") +``` + +函数flag.StringVar接受 4 个参数。 + +- 第 1 个参数是用于存储该命令参数值的地址,具体到这里就是在前面声明的变量name的地址了,由表达式&name表示。 + +- 第 2 个参数是为了指定该命令参数的名称,这里是name。 + +- 第 3 个参数是为了指定在未追加该命令参数时的默认值,这里是everyone。 + +- 第 4 个函数参数,即是该命令参数的简短说明了,这在打印命令说明时会用到。 + + + +## 传入参数,查看参数的使用说明 + +如果我们把上述代码存成名为 demo2.go 的文件,那么运行如下命令就可以为参数name传值: + +```go +go run demo2.go -name="Robert" +``` + +运行后,打印到标准输出(stdout)的内容会是: + +```shell +Hello, Robert! +``` + +另外,如果想查看该命令源码文件的参数说明,可以这样做: + +```shell +$ go run demo2.go --help +``` + +其中的$表示我们是在命令提示符后运行go run命令的。运行后输出的内容会类似: + +```shell +Usage of /var/folders/ts/7lg_tl_x2gd_k1lm5g_48c7w0000gn/T/go-build155438482/b001/exe/demo2: + -name string + The greeting object. (default "everyone") +exit status 2 +``` + +你可能不明白下面这段输出代码的意思。 + +```shell +/var/folders/ts/7lg_tl_x2gd_k1lm5g_48c7w0000gn/T/go-build155438482/b001/exe/demo2 +``` + +这其实是go run命令构建上述命令源码文件时临时生成的可执行文件的完整路径。 + +如果我们先构建这个命令源码文件再运行生成的可执行文件,像这样: + +```shell +$ go build demo2.go +$ ./demo2 --help +``` + +那么输出就会是 + +```shell +Usage of ./demo2: + -name string + The greeting object. (default "everyone") +``` + + + +## 自定义参数使用说明 + + + +https://time.geekbang.org/column/article/13159 +``` + +`GO/工作区和GOPATH.md`: + +```md +# 工作区和GOPATH + + + +## GOPATH + +`GOPATH`:若干工作区的路径。是我们自定义的工作空间。 + +可以把GOPATH简单理解成Go语言的工作目录,它的值是一个目录的路径,也可以是多个目录路径,每个目录都代表Go语言的一个工作区。 + +我们可以使用这些工作区,去放置Go语言的源码文件,以及安装(install)后的归档文件(archive file,也就是以`.a`为扩展名的文件)和可执行文件(executable file)。 + + + +## Go 语言源码的组织方式 + +Go 语言的源码也是以**代码包**为基本组织单位的。 + +在文件系统中,这些**代码包**其实是与**目录**一一对应的。由于目录可以有子目录,所以代码包也可以有**子包**。 + +**一个代码包中可以包含任意个以.go 为扩展名的源码文件,这些源码文件都需要被声明属于同一个代码包。** + + + +代码包的名称一般会与源码文件所在的目录同名。**如果不同名,那么在构建、安装的过程中会以代码包名称为准。** + + + +每个代码包都会有导入路径。代码包的导入路径是其他代码在使用该包中的程序实体时,需要引入的路径。在实际使用程序实体之前,我们必须先导入其所在的代码包。具体的方式就是import该代码包的导入路径。就像这样: + +```go +import "github.com/labstack/echo" +``` + +在工作区中,**一个代码包的导入路径实际上就是从 src 子目录**,到该包的实际存储位置的相对路径。 + + + +## 源码安装后的结果 + +源码文件以及安装后的结果文件都会放到哪里呢?我们都知道,源码文件通常会被放在某个工作区的 src 子目录下。 + + + +那么在安装后如果产生了**归档文件(以“.a”为扩展名的文件),就会放进该工作区的 pkg 子目录**;如果产生了可执行文件,就可能会放进该工作区的 bin 子目录。 + + + +### 归档文件存放的具体位置和规则 + +比如,一个已存在的代码包的导入路径是 + +```go +github.com/supermanc88/SDRSBTC, +``` + +那么执行命令 + +```go +go install github.com/supermanc88/SDRSBTC +``` + + + +生成的归档文件的相对目录就是`github.com/supermanc88`,文件名为`SDRSBTC.a` + +![image-20210128150559844](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20210128150559844.png) + + + +再说回来,归档文件的相对目录与 pkg 目录之间还有一级目录,叫做平台相关目录。平台相关目录的名称是由 build(也称“构建”)的目标操作系统、下划线和目标计算架构的代号组成的。就是上面的`windows_amd64` + + + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/2fdfb5620e072d864907870e61ae5f3c.png) + +## 构建和安装 Go 程序的过程 + +构建使用命令`go build`,安装使用命令`go install`。 + +构建和安装代码包的时候都会执行编译、打包等操作,并且,这些操作生成的任何文件都会**先被保存到某个临时的目录**中。 + + + +### 构建 + +如果构建的是库源码文件,那么操作后产生的结果文件只会存在于临时目录中。这里的构建的主要意义在于检查和验证。 + +如果构建的是命令源码文件,那么操作的结果文件会被搬运到源码文件所在的目录中 + + + +### 安装 + +安装操作会先执行构建,然后还会进行链接操作,并且把结果文件搬运到指定目录。 + +进一步说,如果安装的是库源码文件,那么结果文件会被搬运到它所在工作区的 pkg 目录下的某个子目录中。 + +如果安装的是命令源码文件,那么结果文件会被搬运到它所在工作区的 bin 目录中,或者环境变量GOBIN指向的目录中。 +``` + +`GO/库源码文件.md`: + +```md +# 库源码文件 + + + +**库源码文件是不能被直接运行的源码文件,它仅用于存放程序实体,这些程序实体可以被其他代码使用(只要遵从 Go 语言规范的话)。** + +这里的“其他代码”可以与被使用的程序实体在同一个源码文件内,也可以在其他源码文件,甚至其他代码包中。 + + + +我们今天的问题是:**怎样把命令源码文件中的代码拆分到其他库源码文件?** + +如果在某个目录下有一个命令源码文件 demo4.go,如下: + +```go +package main + +import ( + "flag" +) + +var name string + +func init() { + flag.StringVar(&name, "name", "everyone", "The greeting object.") +} + +func main() { + flag.Parse() + hello(name) +} +``` + + + +在main函数中调用了一个叫作hello的函数。 + +函数hello被声明在了另外一个源码文件中,我把它命名为 demo4_lib.go,并且放在与 demo4.go 相同的目录下。如下: + +```go + +// 需在此处添加代码。[1] + +import "fmt" + +func hello(name string) { + fmt.Printf("Hello, %s!\n", name) +} +``` + + + +那么问题来了:注释 1 处应该填入什么代码? + +- 典型回答 + + 填入代码包声明语句package main。因为在同一个目录下的源码文件都需要被声明为属于同一个代码包。 + + 如果该目录下有一个命令源码文件,那么为了让同在一个目录下的文件都通过编译,其他源码文件应该也声明属于main包。 + + 如此一来,我们就可以运行它们了。比如,我们可以在这些文件所在的目录下运行如下命令并得到相应的结果。 + + ```shell + $ go run demo4.go demo4_lib.go + Hello, everyone! + ``` + + 或者,像下面这样先构建当前的代码包再运行。 + + 在这里,我把 demo4.go 和 demo4_lib.go 都放在了一个相对路径为puzzlers/article3/q1的目录中。 + + ```shell + $ go build puzzlers/article3/q1 + $ ./q1 + Hello, everyone! + ``` + + + + + +## 怎样把命令源码文件中的代码拆分到其他代码包? + +我们先不用关注拆分代码的技巧。我在这里仍然依从前面的拆分方法。我把 demo4.go 另存为 demo5.go,并放到一个相对路径为puzzlers/article3/q2的目录中。 + +然后我再创建一个相对路径为puzzlers/article3/q2/lib的目录,再把 demo4_lib.go 复制一份并改名为 demo5_lib.go 放到该目录中。 + +```go +package lib5 + +import "fmt" + +func Hello(name string) { + fmt.Printf("Hello, %s!\n", name) +} +``` + +可以看到,我在这里修改了两个地方。**第一个改动是,我把代码包声明语句由package main改为了package lib5**。注意,我故意让声明的包名与其所在的目录的名称不同。**第二个改动是,我把全小写的函数名hello改为首字母大写的Hello。** + + + +## 代码包的导入路径总会与其所在目录的相对路径一致吗? + +库源码文件 demo5_lib.go 所在目录的相对路径是puzzlers/article3/q2/lib,而它却声明自己属于lib5包。在这种情况下,**该包的导入路径是puzzlers/article3/q2/lib,还是puzzlers/article3/q2/lib5**? + + + +这个问题往往会让 Go 语言的初学者们困惑,就算是用 Go 开发过程序的人也不一定清楚。我们一起来看看。 + +首先,我们在构建或者安装这个代码包的时候,提供给go命令的路径应该是目录的相对路径,就像这样: + +```shell +go install puzzlers/article3/q2/lib +``` + +该命令会成功完成。之后,当前工作区的 pkg 子目录下会产生相应的归档文件,具体的相对路径是: + +```shell +pkg/darwin_amd64/puzzlers/article3/q2/lib.a +``` + +其中的darwin_amd64就是我在讲工作区时提到的平台相关目录。可以看到,这里与源码文件所在目录的相对路径是对应的 + + + +为了进一步说明问题,我需要先对 demo5.go 做两个改动。 + +第一个改动是,在以import为前导的代码包导入语句中加入puzzlers/article3/q2/lib,也就是试图导入这个代码包。 + +第二个改动是,把对hello函数的调用改为对lib.Hello函数的调用。其中的lib.叫做限定符,旨在指明右边的程序实体所在的代码包。不过这里与代码包导入路径的完整写法不同,只包含了路径中的最后一级lib,这与代码包声明语句中的规则一致。 + +如下: + +```go +package main + +import ( + "flag" + "puzzlers/article3/q2/lib" +) + +var name string + +func init() { + flag.StringVar(&name, "name", "everyone", "The greeting object.") +} + +func main() { + flag.Parse() + lib.Hello(name) +} +``` + +现在,我们可以通过运行go run demo5.go命令试一试。错误提示会类似于下面这种。 + +```shell +./demo5.go:5:2: imported and not used: "puzzlers/article3/q2/lib" as lib5 +./demo5.go:16:2: undefined: lib +``` + +注意,这里还有另外一个线索,那就是**“as lib5”**。这说明虽然导入了代码包puzzlers/article3/q2/lib,但是**使用其中的程序实体的时候应该以lib5.为限定符**。这也就是第二个错误提示的原因了。Go 命令找不到lib.这个限定符对应的代码包。 + + + +为什么会是这样?根本原因就是,我们在源码文件中声明所属的代码包与其所在目录的名称不同。请记住,**源码文件所在的目录相对于 src 目录的相对路径就是它的代码包导入路径,而实际使用其程序实体时给定的限定符要与它声明所属的代码包名称对应。** + + + +## 什么样的程序实体才可以被当前包外的代码引用? + +超级简单,**名称的首字母为大写的程序实体才可以被当前包外的代码引用,否则它就只能被当前包内的其他代码引用。** +``` + +`GO/程序实体的那些事.md`: + +```md +# 程序实体的那些事 + +## Go 语言的类型推断可以带来哪些好处? + +Go 语言的类型推断可以明显提升程序的灵活性,使得代码重构变得更加容易,同时又不会给代码的维护带来额外负担(实际上,它恰恰可以避免散弹式的代码修改),更不会损失程序的运行效率。 + +类型推断提升了程序的灵活性,但是也意味着可读性会降低,有利有弊,需要权衡 + + + + + +## 变量的重声明是什么意思? + +这涉及了短变量声明。通过使用它,我们可以对同一个代码块中的变量进行重声明。 + +> 既然说到了代码块,我先来解释一下它。在 Go 语言中,代码块一般就是一个由花括号括起来的区域,里面可以包含表达式和语句。Go 语言本身以及我们编写的代码共同形成了一个非常大的代码块,也叫全域代码块。这主要体现在,只要是公开的全局变量,都可以被任何代码所使用。相对小一些的代码块是代码包,一个代码包可以包含许多子代码包,所以这样的代码块也可以很大。 +> +> 接下来,每个源码文件也都是一个代码块,每个函数也是一个代码块,每个if语句、for语句、switch语句和select语句都是一个代码块。甚至,switch或select语句中的case子句也都是独立的代码块。走个极端,我就在main函数中写一对紧挨着的花括号算不算一个代码块?当然也算,这甚至还有个名词,叫“空代码块”。 + +回到变量重声明的问题上。其含义是对已经声明过的变量再次声明。变量重声明的前提条件如下。 + +1. 由于变量的类型在其初始化时就已经确定了,所以对它再次声明时赋予的类型**必须与其原本的类型相同**,否则会产生编译错误。 +2. 变量的重声明只**可能发生在某一个代码块中**。如果与当前的变量重名的是外层代码块中的变量,那么就是另外一种含义了 +3. 变量的重声明**只有在使用短变量声明时才会发生**,否则也无法通过编译。如果要在此处声明全新的变量,那么就应该使用包含关键字var的声明语句,但是这时就不能与同一个代码块中的任何变量有重名了。 +4. 被“声明并赋值”的变量**必须是多个,并且其中至少有一个是新的变量**。这时我们才可以说对其中的旧变量进行了重声明。 + +这样来看,变量重声明其实算是一个语法糖(或者叫便利措施)。它允许我们在使用短变量声明时不用理会被赋值的多个变量中是否包含旧变量。可以想象,如果不这样会多写不少代码。 + + + +例: + +```go +var err error +n, err := io.WriteString(os.Stdout, "Hello, everyone!\n") +``` + +使用短变量声明对新变量n和旧变量err进行了“声明并赋值”,这时也是对后者的重声明。 + + + +## 如果一个变量与其外层代码块中的变量重名会出现什么状况? + +例: + +```go +package main + +import "fmt" + +var block = "package" + +func main() { + block := "function" + { + block := "inner" + fmt.Printf("The block is %s.\n", block) + } + fmt.Printf("The block is %s.\n", block) +} +``` + +这个命令源码文件中有四个代码块,它们是:全域代码块、main包代表的代码块、main函数代表的代码块,以及在main函数中的一个用花括号包起来的代码块。 + + + +我在后三个代码块中分别声明了一个名为block的变量,并分别把字符串值"package"、"function"和"inner"赋给了它们。此外,我在后两个代码块的最后分别尝试用fmt.Printf函数打印出“The block is %s.”。这里的“%s”只是为了占位,程序会用block变量的实际值替换掉。 + +### 回答 + +运行后打印出的内容是: + +```shell +The block is inner. +The block is function. +``` + +### 解析 + +**声明重名的变量是无法通过编译的,用短变量声明对已有变量进行重声明除外,但这只是对于同一个代码块而言的。** + +对于不同的代码块来说,其中的变量重名没什么大不了,照样可以通过编译。即使这些代码块有直接的嵌套关系也是如此. + + + +我引用变量时到底用的是哪一个? + +这其实有一个很有画面感的查找过程。这个查找过程不只针对于变量,还适用于任何程序实体。如下面所示。 + +- 首先,代码引用变量的时候总会最优先查找当前代码块中的那个变量。注意,这里的“当前代码块”仅仅是引用变量的代码所在的那个代码块,并不包含任何子代码块。 +- 其次,如果当前代码块中没有声明以此为名的变量,那么程序会沿着代码块的嵌套关系,从直接包含当前代码块的那个代码块开始,一层一层地向外查找。 +- 一般情况下,程序会一直查到当前代码包代表的代码块。如果仍然找不到,那么 Go 语言的编译器就会报错了。 + + + +> 还记得吗?如果我们在当前源码文件中导入了其他代码包,那么引用其中的程序实体时,是需要以限定符为前缀的。所以程序在找代表变量未加限定符的名字(即标识符)的时候,是不会去被导入的代码包中查找的。 +> +> 但有个特殊情况,如果我们把代码包导入语句写成import . "XXX"的形式(注意中间的那个“.”),那么就会让这个“XXX”包中公开的程序实体,被当前源码文件中的代码,视为当前代码包中的程序实体。 比如,如果有代码包导入语句import . fmt,那么我们在当前源码文件中引用fmt.Printf函数的时候直接用Printf就可以了。在这个特殊情况下,程序在查找当前源码文件后会先去查用这种方式导入的那些代码包。 + + + +从作用域的角度也可以说,虽然通过`var block = "package"`声明的变量作用域是整个main代码包,但是在main函数中,它却被那两个同名的变量**“屏蔽”**了。 + + + +## 不同代码块中的重名变量与变量重声明中的变量区别到底在哪儿? + +为了方便描述,我就把**不同代码块中的重名变量叫做“可重名变量”**吧。注意,在同一个代码块中不允许出现重名的变量,这违背了 Go 语言的语法。 + +1. 变量重声明中的变量一定是在某一个代码块内的。注意,这里的“某一个代码块内”并不包含它的任何子代码块,否则就变成了“多个代码块之间”。而可重名变量指的正是在多个代码块之间由相同的标识符代表的变量。 +2. 变量重声明是对同一个变量的多次声明,这里的变量只有一个。而可重名变量中涉及的变量肯定是有多个的。 +3. 不论对变量重声明多少次,其类型必须始终一致,具体遵从它第一次被声明时给定的类型。而可重名变量之间不存在类似的限制,它们的类型可以是任意的。 +4. 如果可重名变量所在的代码块之间,存在直接或间接的嵌套关系,那么它们之间一定会存在“屏蔽”的现象。但是这种现象绝对不会在变量重声明的场景下出现。 + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/5e68210d5639f9e42738f21bd9eb1e89.png) + +## 怎样判断一个变量的类型? + +```go +package main + +import "fmt" + +var container = []string{"zero", "one", "two"} + +func main() { + container := map[int]string{0: "zero", 1: "one", 2: "two"} + fmt.Printf("The element is %q.\n", container[1]) +} +``` + +那么,怎样在打印其中元素之前,正确判断变量container的类型? + + + +### 回答 + +答案是使用“类型断言”表达式。具体怎么写呢? + +```go +value, ok := interface{}(container).([]string) +``` + +这里有一条赋值语句。在赋值符号的右边,是一个类型断言表达式。 + +它包括了用来把container变量的值转换为空接口值的interface{}(container)。 + +以及一个用于判断前者的类型是否为切片类型 []string 的 .([]string)。 + +这个表达式的结果可以被赋给两个变量,在这里由value和ok代表。变量ok是布尔(bool)类型的,它将代表类型判断的结果,true或false。 + +如果是true,那么被判断的值将会被自动转换为[]string类型的值,并赋给变量value,否则value将被赋予nil(即“空”)。 + +顺便提一下,这里的ok也可以没有。也就是说,类型断言表达式的结果,可以只被赋给一个变量,在这里是value。 + +但是这样的话,当判断为否时就会引发异常。 + +### 解析 + +正式说明一下,类型断言表达式的语法形式是x.(T)。其中的x代表要被判断类型的值。这个值当下的类型必须是接口类型的,不过具体是哪个接口类型其实是无所谓的。 + +所以,当这里的container变量类型不是任何的接口类型时,我们就需要先把它转成某个接口类型的值。 + +如果container是某个接口类型的,那么这个类型断言表达式就可以是container.([]string)。这样看是不是清晰一些了? + +在 Go 语言中,interface{}代表空接口,任何类型都是它的实现类型。 +``` + +`GameMaker/The Asset Editors/Animation Curve.md`: + +```md +# The Animation Curve Editor 椭圆曲线编辑器 + +![The Animation Curve Editor](images/Animation Curve/Editor_Animation_Curves.png) + + + +译者加: + +![image-20210222171930149](images/Animation Curve/image-20210222171930149.png) + + + +动画曲线是一种资产,包含一条或多条代表值随时间变化的曲线,可以使用线性插值,平滑插值或贝塞尔曲线插值在曲线的不同点之间移动。您在垂直轴上设置的值可以介于任意两个值之间(默认情况下介于-1和1之间),沿水平轴的持续时间始终以0到1的形式进行归一化,从而可以轻松地在您的乘数中使用不同的时间范围来定位不同的时间范围游戏代码或将其拉伸以适合“序列编辑器”中的曲目。 + +动画曲线由“通道”组成,每个通道可以有自己的曲线设置,例如,您可以-用两个通道描述x / y位置的空间位置,或用4个通道代表一个通道的颜色梯度。颜色格式。动画曲线资产可以在使用序列时使用,也可以使用代码进行访问,从而使它们成为创建游戏时的强大工具。 + +首次创建动画曲线素材时,编辑器窗口将打开,并向您显示以下部分: + + + +## Curve Name + +在这里,您可以为动画曲线指定一个唯一的名称(仅字母数字字符和“ _”下划线符号)。通过代码或按顺序访问曲线时,将使用该名称来引用曲线及其所有通道。 + + + +## Channel List + +您可以在通道列表中添加和命名将构成动画曲线资产的不同通道。 默认情况下,首次创建动画曲线时,会为您添加一个通道,您可以通过单击底部的“添加通道”按钮来添加其他通道。每个通道必须具有唯一的名称(仅使用字母数字字符和下划线符号_),并且您还可以通过双击左侧色样上的来设置通道的颜色,这将打开颜色选择器,让您选择新的颜色。要重命名频道,您可以双击并重命名,还可以使用鼠标右键打开菜单,您可以在其中选择重命名,更改颜色或删除频道。 + +通过单击名称旁边的箭头以显示通道曲线上的每个点,可以扩展通道,可以通过单击当前值并输入新值来手动编辑这些点: + +![Expanded Channel List](images/Animation Curve/Editor_Animation_Curves_ExpandList.png) + +您可以通过点击“眼睛”图标![Eye Icon](images/Animation Curve/Icon_ToggleVisibility.png)切换频道可见性,或者通过点击“ bin”图标![Delete Channel Icon](images/Animation Curve/Icon_DeleteChannel.png)删除频道。您还可以通过单击通道类型图标并选择以下选项之一来更改通道从一点到另一点的流动方式: + + + +- 线性:这将在通道上的点之间创建线性级数 +- 平滑:这将使用catmull-rom插值法在通道上的点之间创建平滑的渐变 +- Bezier:这将使用Bézier曲线数学在通道上的点之间创建平滑的渐变,您可以在该点上通过操纵点上的“手柄”来控制曲线的外观。 + + + +![Example using the Channel List buttons](images/Animation Curve/Editor_Animation_Curves_Buttons.gif) + + + +**请注意,更改通道的曲线类型会将这种类型应用于所有通道。** + + + +## Channel Range + +通道范围按钮用于设置通道编辑器的可视垂直范围。默认情况下,它将设置为-1到1,但是单击此按钮将打开以下窗口: + +![Animation Curve Editor Channel Range](images/Animation Curve/Editor_Animation_Curves_Range.png) + +更改这些值将更改“通道编辑器”部分中数据的表示方式,使您沿通道曲线可以具有比-1到1高或低的值。请注意,这纯粹是视觉上的,并不用于限制通道的值。 + + + +## Channel Editor + +通道编辑器显示了动画曲线中使用的不同通道的直观表示。默认情况下,所有通道都将显示在此处,但是您只能编辑当前从“通道列表”中选择的通道,该列表将在编辑器中突出显示。您可以通过在通道曲线上单击并在编辑器中将其拖动来更改沿曲线的任何点的位置,但是请注意,第一个和最后一个点只能沿垂直轴更改,并且其水平值始终为0和1。 + +您可以通过以下方式将点直接添加到通道曲线中:将鼠标移至主通道线附近,然后单击``当光标变为``添加点''工具时,然后可以通过单击并拖动或更改其值来编辑这些点在频道列表的展开的频道选项中: + +![img](images/Animation Curve/Editor_Animation_Curves_AddPoint.gif) + + 您可以通过单击并在编辑器中拖动以选择所有要编辑的点来一次编辑多个点,或者使用![CTRL Icon](images/Animation Curve/Icon_Ctrl.png)/![CMD Icon](images/Animation Curve/Icon_Cmd.png) +![LMB Icon](images/Animation Curve/Icon_LMB.png)将点单独添加到选择中。释放鼠标按钮,然后再次单击所选内容中的任何点并拖动将它们一起移动: + +![Selecting and moving multiple points on a channel curve](images/Animation Curve/Editor_Animation_Curves_Moving.gif) + + + +## Zoom Controls + +缩放控件允许您在通道编辑器中扩展![Zoom Out Icon](images/Animation Curve/Icon_ZoomOut.png)或收缩![Zoom In Icon](images/Animation Curve/Icon_ZoomIn.png)通道曲线的垂直比例。 + +![Using the Zoom controls](images/Animation Curve/Editor_Animation_Curves_Zoom.gif) +``` + +`GameMaker/The Asset Editors/Objects/Object Events/Async Events/Audio Playback.md`: + +```md +# Audio Playback + +![Audio Playback Event](images/Audio Playback/Async_AudioPlayback.png) + +此事件只能由音频队列播放功能触发,并将返回存储在变量async_load中的DS Map,其中包含与触发事件的音频队列相关的不同键/值对。当您选择要播放的音频队列并且该音频队列中的缓冲区已播放完毕时,该事件由回调触发。 + +DS映射中将提供以下键: + +- "**queue_id**" - 函数audio_create_play_queue()返回的已完成播放队列的队列索引。 +- "**buffer_id**" - 不再从中播放的缓冲区的缓冲区ID。 +- "**queue_shutdown**" - 由于已调用audio_free_play_queue(),因此在正常播放期间将其设置为0,在接收到事件时将其设置为1。设置为1时,您不想排队其他任何数据。 + +由于可以从多个缓冲区创建音频队列,因此在到达缓冲音频的每个部分的末尾时,可以为一个队列多次触发此事件,因此需要使用“ buffer_id”键。 + +> 注意:变量async_load仅在异步事件中有效,因为指向的DS映射是在事件开始时创建的,然后在末尾再次删除,此变量重置为值-1。但是,必须使用适当的函数清除从事件创建的所有其他数据结构。 + +在下面给出的使用示例中,我们将创建一个音频队列,并添加10种缓冲的音频声音,然后播放该队列: + +```javascript +audio_queue = audio_create_play_queue(buffer_s16, 11052, audio_mono); +for (var i = 0; i < 10; i++;) + { + audio_queue_sound(audio_queue, audio_buffer[i], 0, buffer_get_size(audio_buffer[i])); + } +audio_play_sound(audio_queue, 0, true); +``` + +现在,检测到录制的每个游戏步骤都会触发一个异步音频录制事件,您可以在其中处理录制的输入,如下所示: + +```javascript +var queue = async_load[? "queue_id"]; +var num = 0; +if queue = audio_queue + { + for (var i = 0; i < 10; i++;) + { + if async_load[? "buffer_id"] == audio_buffer[i] + { + buffer_seek(audio_buffer[i], buffer_seek_start, 0); + num = i; + } + } + if num == 9 + { + audio_stop_sound(audio_queue); + audio_free_play_queue(audio_queue); + } + } +``` + +在这里,我们检查队列ID,如果它是我们想要的队列ID,则我们检查缓冲区ID,以查看哪个缓冲区已完成播放。然后,我们还将本地var设置为该索引值以供以后检查,并将缓冲区重新设置为该声音的开始。一旦我们的局部变量达到9,表明队列已完成,我们将停止播放声音并释放队列。 +``` + +`GameMaker/The Asset Editors/Objects/Object Events/Draw Events.md`: + +```md +# The Draw Events + +![The Draw Event](images/Draw Events/Editor_Objects_Draw.png) + +绘画事件类别是具有所有事件的类,该事件控制着您在运行游戏时在屏幕上看到的内容。它分为多个单独的事件,以更好地满足您游戏的不同绘画要求,下图说明了每个事件的执行顺序: + +![Object Editor Draw Diagram](images/Draw Events/Editor_Events_DrawOrder.png) + + 普通的Draw事件分为三个子类型:Draw Begin,标准Draw和Draw End事件。通常,您只需要使用标准的Draw事件,并且您应该知道房间中的所有实例都会在游戏的每个步骤触发此事件,因此只要实例具有可见的外观,便始终会调用此事件。标志设置为true。重要的是要理解,即使您没有在对象属性中为该子事件定义任何内容(即,没有代码或动作),如果为对象分配了精灵,则它将仍然具有将触发的绘制事件。这是因为GameMaker Studio 2具有两种在Draw事件中绘制内容的方式: + +- 默认绘制,即当您在对象属性中定义精灵并在常规Draw事件中不放置任何动作或代码时,在这种情况下,GameMaker Studio 2将自动绘制分配的精灵。请注意,只要绘制事件为空,您在其他事件中执行的任何更改图像缩放比例,索引,混合等的变换都将反映出来。 +- 自定义绘图,这是您在绘图事件中放置代码或动作的时间。这是在告诉GameMaker Studio 2'我想控制您为该对象实例绘制的内容',并且它完全覆盖默认绘制。这意味着,例如,您可以为一个对象指定一个sprite,然后设置draw事件以绘制文本,并且该文本将在屏幕上绘制,但是由于您没有告知GameMaker Studio 2,因此不会绘制该Sprite。将其与文本一起绘制。 + +其余的绘制事件将在下面列出的部分中进行说明: + + + +## Draw Begin And Draw End + +除了主要的Draw事件之外,您还具有Draw Begin和Draw End事件。它们的行为与标准Draw事件完全相同(即:它们将在游戏的每个步骤以及所有视口中绘制您放置在它们中的任何内容),但是如果未将它们明确添加到屏幕上,则它们不会“默认绘制”任何内容。对象,它们将始终在标准Draw事件之前/之后运行。因此,在游戏运行时,您将始终对所有实例具有Draw Begin事件,然后对所有实例具有Draw事件,最后对所有实例具有Draw End事件。 + +这样,您可以在一个实例的“绘制开始”步骤中绘制事物或设置绘制属性,并确保所有具有标准Draw事件或Draw End事件的实例都将使用这些属性或绘制第一个实例所绘制的内容。 基本上,这是一种确定的方法,可以确保在某些时间绘制某些内容,就像“开始步骤”和“结束步骤”事件一样。 + +在GameMaker Studio 2中进行绘制时,需要注意一些事项,无论是正在绘制的精灵,着色器还是3D缓冲区: + +- 绘制事件是一个非常密集的事件,因为它是占用大量时间和资源的事件之一……为此,在绘制事件中除了绘制之外,不做任何事情都是一个好主意。因此,请为Step事件或Alarms或其他适合的事件保存大型代码或复杂的操作,但最好将draw事件保留在绘图中,因为这样做才是最好的。 +- 如果您的对象的visible参数设置为off(以便不绘制该对象的实例),则将跳过所有绘制事件(“调整大小”事件除外)。这意味着使实例不可见将阻止运行任何这些事件中的所有代码,因此,如果基本代码不可见,请不要将必需的代码放入draw事件中。 +- 绘制的内容与您选择的碰撞引擎(传统的或物理的)无关,这是由对象属性和已赋予对象的精灵(或蒙版)定义的。 + +## Draw GUI + +Draw GUI事件类型属于Draw Event类别,是专门为不受视图摄像机比例或旋转影响的图形GUI元素设计的。这意味着您可以让实例为HUD或用户界面绘制所有元素,而不必将元素的所有位置都基于房间内的实例位置或当前摄像机视图的位置。 + +在这种情况下进行绘制时,重要的是要了解即使在摄像机视图处于活动状态时绘制坐标也不会改变,并且(0,0)始终是应用程序表面或显示屏的左上角(请参见此部分的底部),并且应用程序表面的默认宽度和高度为1:1。深度顺序仍然保持在不同层上的不同实例之间(因此,较高层的实例将被绘制在较低层的一个实例之下)以及事件本身内,因为Draw GUI Begin事件将首先为所有实例绘制,然后标准Draw GUI将为所有实例绘制图形,最后触发Draw Gui End事件。 + +这样,您可以在一个实例中的Draw GUI Begin中绘制事物或设置draw属性,并确保具有标准Draw GUI或Draw GUI End事件的所有实例都将使用这些属性或覆盖第一个实例绘制的内容。基本上,这是一种确保在特定时间绘制某些东西的肯定方法,就像``开始步骤''和``结束步骤''事件一样。 + +这些事件也可以与正常的Draw事件一起使用(通常会受到摄影机视图位置,比例和旋转的影响)。如果您没有Draw事件,但确实有Draw GUI事件,则GameMaker Studio 2仍将照常默认为该实例绘制精灵(如果有)。 + +与其他绘制事件有关,Draw GUI事件将始终覆盖正常绘制事件中绘制的所有内容。因此,如果您在较低层上有一个带有Draw GUI事件的实例,则它将通过常规绘图事件在较高层上的实例上进行绘制。如果两个实例都具有Draw GUI事件,则将遵守图层顺序。 + +> 注意:默认情况下,此事件将使用应用程序表面尺寸(通常是房间或视口的尺寸)绘制1:1。这意味着,当您在“游戏选项”中启用了“宽高比校正”时,将不会在“字母框”游戏的黑条上绘制GUI。可以使用display_set_gui_maximise()函数关闭此行为,并且还可以将GUI事件锁定为特定大小,然后使用display_set_gui_size()函数将其缩放为适合显示或应用程序表面尺寸的大小。 + +## Pre And Post Draw + +Draw Pre和Post Draw事件属于Draw Event类别。但是,与其他Draw事件不同,它们直接绘制到显示缓冲区,这将是当前可见的所有视口的合并屏幕空间的大小,或者如果仅使用一个视口或根本不使用任何视口,则为窗口大小。下图说明了这一点: + +![Object Editor Post Draw](images/Draw Events/Editor_Object_PostDraw.png) + + 因此,如果您使用的是Pre或Post绘制事件,则将绘制到全屏渲染目标(显示缓冲区),该目标的大小将与所有视口都适合的窗口大小相同。如果没有活动的视口,则将其设置为窗口本身的大小。 + +Pre Draw事件在任何其他draw事件之前触发,您可以在其中设置值,设置draw属性甚至绘制东西,而不必担心视口或GUI层的大小(GUI层的大小可能与屏幕缓冲区,但可能不是,因为您可以在代码中设置GUI分辨率。 + +值得注意的是,此事件发生在清除常规图形的显示缓冲区之前,这意味着如果您不关闭房间编辑器中的视图清除功能,则在Pre Draw事件中绘制的任何内容都不会被看到,这是第一个视口绘制会清除它。如果您想自己查看视口,或者根本不使用视口,那么还应该在``房间编辑器''中将背景色设置为alpha为0。 + +> 注意:如果关闭这些选项,则在测试游戏时,您可能会注意到屏幕上绘制了不必要的假象(例如实例中的“拖尾”)。这是因为您直接在显示缓冲区的前一帧上绘制而未清除它。但是您可以使用draw_clear_alpha()自己执行此操作。 + +在所有标准绘图事件之后但在Draw GUI事件之前触发Post Draw事件。与Pre Draw事件类似,它基于显示缓冲区的大小,并置于Draw GUI事件之前,使您能够简单,轻松地在不干扰任何HUD的情况下以全屏方式执行后处理效果和其他操作。 /游戏中可能包含的GUI元素。 + +## Window Resize + +此事件类型属于“绘制事件”类别,尽管它实际上并未绘制任何内容,但它确实对显示缓冲区的变化做出了反应-特别是,它设计用于在游戏窗口时对UWP显示缓冲区大小的变化做出反应。被“捕捉”。 + +在UWP目标平台上,重要的是要从游戏窗口的“捕捉”中捕获显示缓冲区的任何大小调整(例如,当用户将窗口拖动到显示器的侧面时)。此事件将执行此操作,并在每次更改窗口时触发该事件,使您可以在此处添加自定义代码,该代码将调整视图的大小或根据需要重新定位HUD元素。提供此功能是避免步骤事件代码始终检查这些事件的便捷方法。 + +> 注意:您不能在此事件中绘画!它是通过调整窗口大小来触发的,并且仅用于捕获此更改...应进行的任何绘制仍必须处于其他绘制事件之一中。 +``` + +`GameMaker/The Asset Editors/Objects/Object Events/Event Order.md`: + +```md +# Event Order 事件触发顺序 + + 在考虑GameMaker Studio 2中的事件时,应注意,不能明确说明每个事件将要发生的确切顺序,仅因为它取决于GameMaker Studio 2的内部工作原理,并且可能会随着软件的发展发生变化。但是,某些事件将始终以相同的顺序运行。 + +总是以相同方式发生的**第一组事件是首次进入房间时发生的事件**。不同事件将触发的顺序是: + +- **Object Variables / Instance Variables are initialised** 对象变量/实例变量初始化(这些变量可以在对象编辑器或房间编辑器的IDE中定义)。如果您有一个带有变量的对象,则将在其他所有对象之前创建它们,然后,将在之后定义任何带变量的实例,因此您可以在房间编辑器中用实例特定的对象覆盖对象变量。 +- **CreateEvent** 创建事件后跟每个实例的实例创建代码-因此,在创建每个实例时,它将首先运行其创建事件,然后运行其实例创建代码,然后再继续下一个要创建的实例 +- **Game Start Event** 游戏开始事件-从房间编辑器放置在房间的所有实例中,在游戏的第一个房间中触发一次该事件(请注意,调用game_restart()将再次触发此事件。 +- **Room Creation Code** 房间创建代码-这是在“房间编辑器”中编写的一次性代码,用于首次输入房间时 +- **Room Start Event of all instances** 所有实例的“房间开始事件”-事件的“other”类别之一,将针对所有实例(持久性或其他)触发 + +还要注意的是,您还可以通过在实例编辑器属性窗口的列表中上移或下移来设置特定实例在房间编辑器中的创建顺序。 + +除了那些特定的事件,无论属于三种step事件和不同的draw事件,唯一已知的顺序将始终以相同的方式发生。这些将始终保持一致,因此,如果您的代码在游戏的每个步骤中都依赖于特定的时间安排,则应使用: + +- **Begin Step Event** +- **Step Event** 请注意,步骤事件是在实例放到新位置之前执行的 +- **End Step Event** + +所有用于绘制的事件也总是按以下相同顺序处理(“窗口调整大小”事件除外,该事件的触发方式不同): + +- **Pre Draw Event** +- **Draw Begin Event** +- **Draw Event** +- **Draw End Event** +- **Post Draw Event** +- **Draw GUI Begin Event** +- **Draw GUI Event** +- **Draw GUI End Event** + + +``` + +`GameMaker/The Asset Editors/Objects/Object Events/Gesture Events.md`: + +```md + + +# The Gesture Events 手势事件 + +![The Gesture Events](images/Gesture Events/Editor_Objects_Gesture.png) + +每当GameMaker Studio 2检测到鼠标的“手势”或触摸屏事件时,都会触发“手势事件”类别中的事件(尽管这些手势事件是专门为移动设备设计的,但它们也可以用于其他目标上以检测鼠标,尽管在这种情况下他们不会检测到多次触摸)。手势系统的目标是尝试在比直接鼠标\\触摸读取功能更高的级别上识别输入,并旨在简化基于触摸的设备上常用输入的实现。 + +>注意:由于缺少对在该平台上使用这些事件的多点触摸的支持,因此不会在HTML5平台上触发这些事件。如果要在该目标上寻找手势,则应该使用设备功能。 + +您可以选择检测实例手势还是全局手势,其中仅当初始触摸/单击位于房间中的实例上时才会触发实例手势事件。请注意,实例必须具有有效的碰撞遮罩(有关更多详细信息,请参见“ Sprite编辑器-碰撞遮罩”和“对象编辑器-碰撞遮罩”部分)以触发此事件。 但是,全局事件将通过在游戏室内的任何地方以及所有具有该事件的实例上触摸/单击来触发。 + +识别手势后,它将触发一个或多个可用事件,并且触发的事件将取决于已检测到的手势类型。但是在每种情况下,都会为您生成一个DS Map,并将其存储在内置变量event_data中。可用的密钥将取决于创建该事件的类型,并在下面的每个小节中显示。 + +> 注意:变量event_data仅在这些事件中有效,因为它指向的DS映射在事件开始时自动创建,然后在末尾再次销毁,并且此变量的值全部重置为-1。其他时间。 + +值得注意的是,如果您在触摸的位置下有多个实例,并且它们都具有手势事件,那么它们都会触发,而不仅仅是“最顶层”的实例。 还要注意,当使用多个摄像机视图并拖动一个实例时,返回的值将基于您收到初始触摸/单击时所在的视图-对于该实例在该手势中的所有后续事件都是如此。因此,在一个视图中触摸并拖动实例,然后在另一视图中释放触摸,将返回相对于首次检测到手势的初始视图的值。 + +## ![img](images/Gesture Events/Icon_Tap.png) ![img](images/Gesture Events/Icon_Drag.png) ![img](images/Gesture Events/Icon_Flick.png)Tap, Drag, And Flick Events + +“ Tap”,“ Drag”和“ Flick”事件均基于在屏幕上的单次触摸或鼠标单击,而event_data DS Map将包含以下键: + +| Key | Description | +| ------------------- | ------------------------------------------------------------ | +| "**gesture**" | 这是一个ID值,对于正在播放的手势是唯一的。这使您可以将多部分手势的不同部分(例如,拖动开始,拖动和拖动结束)链接在一起。 | +| "**touch**" | 这是用于手势的触摸的索引。通常,此操作将从0开始并针对每个被按下的手指增加,然后在所有手指移开时重置为0,但是如果用户在另一次触摸触发此事件时正在其他任何地方触摸屏幕,则该值将大于0。 | +| "**posX**" | 这是触摸的房间空间X位置。 | +| "**posY**" | 这是触摸的房间空间Y位置 | +| "**rawposX**" | 这是触摸的原始窗口空间X位置(相当于使用device_mouse_raw_x()获取鼠标位置)。 | +| "**rawposY**" | 这是触摸的原始窗口空间Y位置(等同于使用device_mouse_raw_y()获取鼠标位置)。 | +| "**guiposX**" | 这是触摸的gui空间X位置(等效于使用device_mouse_x_to_gui()获取鼠标位置)。 | +| "**guiposY**" | 这是触摸的GUI空间的Y位置(等同于使用device_mouse_y_to_gui()获取鼠标位置)。 | +| "**diffX**" | 这是此手势中当前触摸位置和最后触摸位置之间的空间X差。 | +| "**diffY**" | 这是此手势中当前触摸的位置和最后一次触摸的位置之间的空间Y差。 | +| "**rawdiffX**" | 这是此手势中当前触摸位置和最后触摸位置之间的原始X差。 | +| "**rawdiffY**" | 这是此手势中当前触摸位置和最后触摸位置之间的原始Y差。 | +| "**guidiffX**" | 这是此手势中当前触摸位置和最后触摸位置之间的gui-space X差。 | +| "**guidiffY**" | 这是此手势中当前触摸的位置和最后触摸的位置之间的gui-space Y差。 | +| "**viewstartposX**" | 这是当前手势的房间X起始位置。 | +| "**viewstartposY**" | 这是当前手势的房间Y起始位置。 | +| "**rawstartposX**" | 这是当前手势的原始X起始位置 | +| "**rawstartposY**" | 这是当前手势的原始Y起始位置。 | +| "**guistartposX**" | 这是当前手势的gui-space X起始位置。 | +| "**guistartposY**" | 这是当前手势的gui空间Y起始位置。 | +| "**isflick**" | 仅在“拖动结束”事件中可用。如果将拖动的结尾检测为轻拂,则将其设置为1,这意味着如果您仍在处理拖动,则不需要单独的Flick Event。 | + +### Tap + +点击事件将在实例被触摸或单击时触发,或者-如果是全局事件,则在游戏注册触摸或单击房间中任何位置时触发。轻击被视为快速触摸并释放,如果触摸持续太长时间,则将被视为拖动(并触发拖动手势事件而不是Tap事件)。此事件将生成一个event_data DS映射,然后可用于获取有关该事件的信息。例如: + +#### Create Event + +```javascript +x_goto = x; +y_goto = y; +``` + +#### Tap Event + +```javascript +x_goto = event_data[? "posX"]; +y_goto = event_data[? "posY"]; +``` + +#### Step Event + +```javascript +var _pd = point_distance(x, y, x_goto, y_goto); +move_towards_point(x_goto, y_goto, clamp(_pd, 0, 5); +``` + +上面的代码将检测屏幕上的轻击,然后获取轻击的位置以将实例移动到该位置。请注意,如果您想要更长或更短的拍子检测时间,则可以使用函数finger_drag_time()进行设置。这将设置从初始检测到轻击变成拖拽之间的时间,因此将其设置为较高的值可以使抽头检测的时间更长,或者将其设置为较低的值可以使抽头检测的时间更短(该值以秒为单位,默认值为0.16)。 + +### Double Tap + +当实例连续连续两次被触摸或单击时(或-如果它是全局事件-当游戏在房间中的任何位置注册两次快速触摸或单击时),将触发Double Tap事件。双击被认为是两次快速触摸并释放,但是如果任何触摸持续时间太长,则将其视为拖动(并触发拖动手势事件而不是双击事件)。此事件将生成一个event_data DS映射,然后可用于获取有关该事件的信息。 例如: + +#### Create Event + +```javascript +x_goto = x; +y_goto = y; +``` + +#### Double Tap Event + +```javascript +instance_destroy(); +``` + +上面的代码只是检测到双击,然后销毁实例。请注意,您可以使用函数gesture_double_tap_time()(其默认值-以秒为单位)为0.16来设置两次点击之间的时间以触发两次点击,还可以使用函数gesture_double_tap_distance()来设置两次点击之间的检测距离。 (如果在此距离之外检测到第二次点击,将被视为常规点击事件)。 + + + +### Drag Start + +当用户保持触摸或单击而不释放它时,将触发“拖动开始”事件。初始触摸后经过设置时间时,它将被触发一次,默认时间是默认值0.16秒(尽管您可以通过使用gesture_drag_time()函数将其设置为以秒为单位的任何其他值)。 触发此事件后,只要用户按住触摸/单击,则拖动事件将在每一步触发,直到释放触摸/单击。此事件将生成一个event_data DS映射,然后可用于获取有关该事件的信息。例如: + +#### Create Event + +```javascript +drag_offsetX = 0; +drag_offsetY = 0; +``` + +#### Drag Start Event + +```javascript +drag_offsetX = x - event_data[?"posX"]; +drag_offsetY = y - event_data[?"posY"]; +``` + +上面的代码使用Drag Start事件获取触摸/单击的位置,并使用它为x和y轴设置偏移值。然后,在拖动实例时可以使用它来确保它不会“跳到”检测到触摸/点击的位置(有关此示例的继续,请参见下面的Draging事件)。 + + + +### Dragging + +Draging事件在Drag Start事件之后触发,并且将在用户保持对实例(或屏幕,如果是全局事件)的情况下保持触摸/单击并且移动超过定义的拖动阈值的每一步触发。默认情况下,此距离为0.1英寸,但可以使用功能finger_drag_distance()进行设置。如果没有动作或动作低于定义的阈值,则不会触发该事件。此事件将生成一个event_data DS映射,然后可用于获取有关该事件的信息。例如: + +#### Create Event + +```javascript +drag_offsetX = 0; +drag_offsetY = 0; +``` + +#### Drag Start Event + +```javascript +drag_offsetX = x - event_data[?"posX"]; +drag_offsetY = y - event_data[?"posY"]; +``` + +#### Dragging Event + +```javascript +x = event_data[?"posX"] + drag_offsetX; +y = event_data[?"posY"] + drag_offsetY; +``` + +上面的示例代码使用在Drag Start事件中设置的偏移变量在触发Draging事件时移动实例。 + + + +### Drag End + +当用户释放实例上的触摸/单击(或事件为全局的屏幕时)时,将触发Drag End事件。该事件将生成一个event_data DS映射,您可以使用该映射获取有关该事件的信息,但是在这种情况下,该映射将具有一个额外的键:“ isflick”。 轻拂的计算方式为发生拖曳的每秒距离,如果“ isflick”键的值大于每秒定义的距离值,则为true,否则为false。 请注意,默认值是每秒2英寸,但是您可以使用函数finger_flick_speed()将其设置为另一个值。还要注意,有一个专用的Flick事件,如果'isflick'变量为true,也会触发该事件。使用的一个例子是: + +#### Create Event + +```javascript +flickVelX = 0.0; +flickVelY = 0.0; +``` + +#### Drag End Event + +```javascript +isFlick = event_data[?"isflick"]; +if (isFlick) + { + flickVelX = event_data[?"diffX"]; + flickVelY = event_data[?"diffY"]; + } +else + { + flickVelX = 0; + flickVelY = 0; + } +``` + +#### Step Event + +```javascript +x += flickVelX; +y += flickVelY; +flickVelX *= 0.7; +flickVelY *= 0.7; +``` + +上面的代码只是简单地获取了上一个Draging事件和当前Drag End事件在x和y位置上的差异,并且如果移动量大于轻拂阈值,它会设置一些变量来用于移动实例。步骤事件。 + + + +### Flick + +仅在按住,拖动然后释放触摸/单击并且最后拖动位置和释放位置之间的距离大于每秒2英寸时,才触发Flick事件(这是默认设置,尽管可以更改)使用手势[flick_flick_speed())函数。此事件将生成一个event_data DS映射,然后可用于获取有关该事件的信息。 例如: + +#### Create Event + +```javascript +flickVelX = 0.0; +flickVelY = 0.0; +``` + +#### Flick Event + +```javascript +flickVelX = event_data[?"diffX"]; +flickVelY = event_data[?"diffY"]; +``` + +#### Step Event + +```javascript +x += flickVelX; +y += flickVelY; +flickVelX *= 0.7; +flickVelY *= 0.7; +``` + +上面的代码只是获取了上一个Draging事件和当前Flick事件在x和y位置上的差值,并且如果移动量大于轻拂阈值,它将设置一些变量以用于在步骤中移动实例事件。 + + + +## ![img](images/Gesture Events/Icon_Pinch.png)Pinch Events + +“捏”事件基于一次触摸到设备屏幕的两次触摸,其中一个(或两个)移动了超过一定距离。 触摸的移动角度以及每次触摸的移动将决定是否检测到捏或旋转事件,其中(对于捏事件类型): + +- 如果其中一个触摸没有移动,则另一个触摸必须在阈值角度范围内朝着它移动或远离它(可以使用函数handle_pinch_angle_towards()和-gesture_pinch_angle_away()进行设置)。 +- 如果两个触摸都在移动,则它们的速度必须在大约相反的方向,并且还要进行相同的角度阈值检查,以确保触摸在大致对齐的情况下移动。 + +当使用上述标准检测到两次触摸和移动时,将触发Pinch事件,并且在每个事件中,将使用以下键填充event_data DS Map: + +| Key | Description | +| --------------- | ------------------------------------------------------------ | +| "gesture" | 这是一个ID值,对于正在播放的手势是唯一的。这使您可以将多部分手势的不同部分(例如,拖动开始,拖动和拖动结束)链接在一起。 | +| "touch1" | 这是用作捏合手势一部分的第一次触摸的索引。通常,该值为0,但是如果用户在另一触摸触发此事件时在其他任何地方触摸屏幕,则该值将大于0。 | +| "touch2" | 这是用作捏合手势一部分的第二次触摸的索引。通常,此值将比touch1的值大1,但也可以是其他一些值,具体取决于在其他位置检测到的触摸数。 | +| "posX1" | 这是第一次触摸的房间空间X位置。 | +| "posY1" | 这是第一次触摸的房间空间Y位置。 | +| "rawposX1" | 这是第一次触摸的原始窗口空间X位置(等同于使用device_mouse_raw_x()获取鼠标位置)。 | +| "rawposY1" | 这是第一次触摸的原始窗口空间Y位置(相当于使用device_mouse_raw_y()获取鼠标位置)。 | +| "guiposX1" | 这是第一次触摸的gui空间X位置(等同于使用device_mouse_x_to_gui()获取鼠标位置)。 | +| "guiposY1" | 这是第二次触摸的gui空间Y位置(等效于使用device_mouse_y_to_gui()获取鼠标位置)。 | +| "posX2" | 这是第二次触摸的房间空间X位置。 | +| "posY2" | 这是第二次触摸的房间空间Y位置。 | +| "rawposX2" | 这是第一次触摸的原始窗口空间X位置。 | +| "rawposY2" | 这是第二次触摸的原始窗口空间Y位置。 | +| "guiposX2" | 这是第二次触摸的GUI空间X位置。 | +| "guiposY2" | 这是第二次触摸的Gui空间Y位置。 | +| "midpointX" | 房间空间中两次触摸之间的中点的X位置。 | +| "midpointY" | 房间空间中两次触摸之间的中点的Y位置。 | +| "rawmidpointX" | 这是中点的原始窗口空间X位置。 | +| "rawmidpointY" | 这是中点的原始窗口空间Y位置 | +| "guimidpointX" | 这是GUI空间X的中点位置。 | +| "guimidpointY" | 这是gui-space Y的中点位置。 | +| "relativescale" | 与此手势中的最后一个事件相比,这是比例差异(因此,对于“捏入”事件,该值始终小于1.0,而对于“捏出”事件,该值始终大于1.0) | +| "absolutescale" | 这是与手势开始时手指所在的位置相比的比例(因此,如果手指之间的距离减半,则为0.5,而如果距离加倍,则为2.0)。 | + +### Pinch Start + +当实例(如果事件为全局事件,则为屏幕)被两个“手指”触摸(并且保持触摸),然后移动一个或两个“手指”时,将触发“捏启动”事件。如果触摸彼此之间的距离大于或等于最小检查距离(默认值为0.1英寸,但可以使用动笔手势_pinch_distance()进行设置),并且它们之间的角度在定义的值内(默认为45°,但可以使用gesture_pinch_angle_towards()和gesture_pinch_angle_away())进行设置,然后会触发Pinch Start事件。在这种情况下,您可以设置变量或存储位置日期以备将来使用。例如: + +#### Pinch Start Event + +```javascript +pinching = true; +pinch_x = event_data[? "midpointX"]; pinch_y = event_data[? "midpointY"]; +``` + +上面的代码将检测到捏合并存储该捏合的中点位置。 + + + +### Pinch In / Pinch Out + +当构成捏合的两次触摸之间的距离变化超过最小阈值(默认设置为+/- 0.1英寸,但是您可以使用函数peer_pinch_distance对其进行更改)时,将在每一步触发“捏合”和“捏合”事件。如果捏触没有动,则这些事件将不会触发。这些事件将生成一个event_data DS映射,然后可用于获取有关该事件的信息。例如: + +#### Global Pinch In /Pinch Out Event + +```javascript +var _scale = event_data[? "relativescale"]; +var _w = camera_get_view_width(view_camera[0]); +var _h = camera_get_view_height(view_camera[0]); +var _x = camera_get_view_x(view_camera[0]) + (_w / 2); +var _y = camera_get_view_y(view_camera[0]) + (_h / 2); + +_w *= _scale; +_h = _w * (room_height / room_width); +_x -= _w / 2; +_y -= _h / 2; + +camera_set_view_pos(view_camera[0], _x, _y); +camera_set_view_size(view_camera[0], _w, _h); +``` + +上面的代码将根据捏合触摸的相对比例缩放视图。 + +### Pinch End + +当用户从设备释放一次(或两次)触摸时,将触发“缩进结束”事件。此事件将生成一个event_data DS映射,您可以使用该映射来获取有关该事件的信息。例如: + +#### Pinch End Event + +```javascript +var _pinchx = event_data[? "midpointX"]; +var _pinchy = event_data[? "midpointY"]; +var _w = camera_get_view_width(view_camera[0]); +var _h = camera_get_view_height(view_camera[0]); +var _x = _pinchx - (_w / 2); +var _y = _pinchy - (_h / 2); + +camera_set_view_pos(view_camera[0], _x, _y); +``` + +上面的代码会将视图位置设置为在释放触摸时构成捏的两次触摸的中点居中。 + + + +## ![img](images/Gesture Events/Icon_Rotate.png)Rotate Events + + “旋转”事件是基于一次识别到设备屏幕的两次触摸,并且在特定时间内两次旋转之间存在一致的角度旋转。触摸的移动角度以及每次触摸的移动将决定是否检测到捏或旋转事件,其中(对于旋转事件类型): + +- 必须在指定的最短时间内按住两次触摸(默认时间为0.16秒,但您可以使用函数finger_rotate_time()对其进行更改)。 +- 在此最短时间内,它们必须沿一致的方向旋转(如果旋转方向在这段时间内发生变化,则不会开始旋转)。 +- 旋转量必须超过最小阈值角度(默认情况下设置为5°,但是可以使用函数gesture_rotate_angle()进行更改)。 + + 当使用上述标准检测到两次触摸和移动时,将触发“旋转事件”,并且在每个事件中,将使用以下键填充event_data DS Map: + +| Key | Description | +| --------------- | ------------------------------------------------------------ | +| "gesture" | 这是一个ID值,对于正在播放的手势是唯一的。这使您可以将多部分手势的不同部分(例如,拖动开始,拖动和拖动结束)链接在一起。 | +| "touch1" | 这是用作捏合手势一部分的第一次触摸的索引。通常,该值为0,但是如果用户在另一触摸触发此事件时在其他任何地方触摸屏幕,则该值将大于0。 | +| "touch2" | 这是用作捏合手势一部分的第二次触摸的索引。通常,此值将比touch1的值大1,但也可以是其他一些值,具体取决于在其他位置检测到的触摸数。 | +| "posX1" | 这是第一次触摸的房间空间X位置。 | +| "posY1" | 这是第一次触摸的房间空间Y位置。 | +| "rawposX1" | 这是第一次触摸的原始窗口空间X位置(等同于使用device_mouse_raw_x()获取鼠标位置)。 | +| "rawposY1" | 这是第一次触摸的原始窗口空间Y位置(相当于使用device_mouse_raw_y()获取鼠标位置)。 | +| "guiposX1" | 这是第一次触摸的gui空间X位置(等同于使用device_mouse_x_to_gui()获取鼠标位置)。 | +| "guiposY1" | 这是第二次触摸的gui空间Y位置(等效于使用device_mouse_y_to_gui()获取鼠标位置)。 | +| "posX2" | 这是第二次触摸的房间空间X位置 | +| "posY2" | 这是第二次触摸的房间空间Y位置。 | +| "rawposX2" | 这是第一次触摸的原始窗口空间X位置。 | +| "rawposY2" | 这是第二次触摸的原始窗口空间Y位置。 | +| "guiposX2" | 这是第二次触摸的GUI空间X位置。 | +| "guiposY2" | 这是第二次触摸的Gui空间Y位置。 | +| "pivotX" | 旋转枢轴点在房间空间中的X位置。 | +| "pivotY" | 旋转枢轴点在房间空间中的Y位置。 | +| "rawpivotX" | 这是旋转枢轴点的原始窗口空间X位置。 | +| "rawpivotY" | 这是旋转枢轴点的原始窗口空间Y位置。 | +| "guipivotX" | 这是旋转枢轴点的gui空间X位置。 | +| "guipivotY" | 这是旋转枢轴点的gui空间Y位置。 | +| "relativeangle" | 与该手势的最后一个事件相比,这是旋转差异,以度为单位 | +| "absoluteangle" | 这是与手势开始时手指所在的位置相比,角度的差异(以度为单位)。因此,例如,如果自手势开始以来手指旋转了四分之一圈,则该值将为90°或-90°,具体取决于旋转方向。 | + +### Rotate Start + +当实例(如果事件为全局事件,则为屏幕)被两个“手指”触摸(并且保持触摸),然后从其开始旋转一个或两个“手指”时,将触发“旋转开始”事件位置。触摸的旋转需要在短时间内开始(默认情况下为0.16秒,但可以使用功能finger_rotate_time()进行设置)并且大于最小角度阈值(默认情况下为5°),但是可以使用手势状态_rotate_angle()进行更改。如果这些检查为真,则将触发Rotate Start事件,您可以使用它存储值或设置变量以与其余的Rotate事件一起使用。例如: + +#### Create Event + +```javascript +rotating = false; +view_a = camera_get_view_angle(view_camera[0]); +``` + + + +#### Rotate Start Event + +```javascript +rotating = true; +``` + +上面的代码只是简单地设置了一些变量来旋转视图相机,然后在Rotate Start事件中将其中一个设置为true。 + +### Rotating + +只要移动幅度大于最小角度阈值(默认为5°,但是可以使用函数gesture_rotate_angle()进行更改),屏幕上的触摸彼此绕过的每一步都会触发Rotating事件。 。此事件可用于设置变量和操作实例,例如: + +#### Rotating Event + +```javascript +var _relangle = event_data[?"relativeangle"]; +var _a = camera_get_view_angle(view_camera[0]); +_a += _relangle; +camera_set_view_angle(view_camera[0], _a); +``` + +上面的代码会根据事件中触摸的旋转运动来旋转相机视图。 + + + +### Rotate End + +当从设备屏幕释放构成手势的一个(或两个)触摸时,将触发Rotate End事件。此事件可用于设置变量和操作实例,例如: + +#### Rotate End Event + +```javascript +rotating = false; +``` + + + +#### Step Event + +```javascript +if !rotating + { + var _a = camera_get_view_angle(view_camera[0]); + var _adif = angle_difference(view_a, _a); + _a += median(-5, _adif, 5); + camera_set_view_angle(view_camera[0], _a); + } +``` + +上面的代码使用Rotate End事件来检测用户何时停止手势,然后设置变量。然后,在step事件中使用此变量将视图摄像机旋转回其原始位置。 + + +``` + +`GameMaker/The Asset Editors/Objects/Object Events/Other Events.md`: + +```md +# The Other Events + +![The Other Events](images/Other Events/Editor_Objects_OtherEvents.png) + +本页说明在对象编辑器的“其他事件“类别下收集的每个不同事件: + +## Outside Room + +当实例离开房间时触发此事件,并且该事件基于对实例的指定精灵(及其属性)进行的检查,因此即使您将图像x或y缩放比例设置为除第一,仅当整个精灵不在屏幕上时才会触发此事件。如果该实例没有精灵,则使用该实例的位置,并且其x或y位置在房间外的那一刻,它也会触发该事件。此事件通常用于子弹之类的东西,一旦它们离开房间,它们就会被销毁,因此您最终不会导致数百万个子弹无限飞走并导致游戏速度减慢。请注意,此事件仅在实例最初离开房间时触发一次。 + +## Intersect Boundary 界面边界 + +当实例“触摸”房间的内部边缘时触发此事件,并且与OutSide Room事件一样,该事件基于对实例的指定精灵进行的检查,因此即使您已设置图像x或如果将y缩放为除1之外的其他值,则在子画面的一部分与房间边界相交时仍会触发此事件。如果实例没有精灵,则使用实例的位置,并且其x或y位置与房间边界相交的那一刻,它也会触发该事件。您可以使用此事件执行操作,例如告诉实例再次“反弹”回房间而不是外面。 + +## Views + +摄像机视图通常在房间编辑器中定义,并且一次只能显示大房间的一小部分。此事件类别有两个单独的子类别(“外部视图”和“相交边界”),每个类别中有八个不同的事件,分别与八个可用的视图端口相对应。这两个类别的功能与相应的房间事件完全相同,只是将摄像机视图的边界作为要检查的实例而不是房间。 + +## Game Start + +该特殊事件在整个游戏中仅触发一次,并且仅针对事件中包含动作或代码的那些实例触发。 这些实例必须存在于游戏的第一个房间中,并已放置在房间编辑器中,而不是使用代码或动作动态创建的。应该注意的是,该事件发生在所有实例的create事件之后,因此可以包含代码或具有该事件先前定义的变量的动作。此事件通常仅在一个“控制器”对象中定义,并且通常用于初始化全局变量,开始音乐,从文件读取以及通常在游戏开始时仅执行一次的任何其他操作。 + +## Game End + +与上述“游戏开始”事件类似,此事件在整个游戏中仅触发一次,并且就在游戏窗口实际关闭之前。 同样,对于具有此事件的实例而言,实际上要执行任何操作都必须在游戏关闭时在房间中进行。请注意,此事件仅在Windows,Ubuntu(Linux)和macOS游戏中才真正可行,因为移动设备可能只是关闭了游戏而没有给GameMaker Studio 2调用的机会。 对于使用HTML5模块制作的浏览器游戏,当关闭游戏选项卡或浏览器时,此事件应起作用,但并非所有浏览器都支持此功能(在撰写本文时,Firefox,Chrome和Safari都支持此功能),因此在使用它时必须格外小心。通常,您将使用此事件保存数据。 + +## Room Start + +房间启动时,最初在房间中的所有实例都会发生此事件。请注意,它发生在创建事件之后,因此可能取决于该事件中先前定义的变量和事物。 + +## Room End + +房间结束时,该事件发生在房间中的所有实例上。此事件对于关卡结束后的“清理”非常有用,例如,您可以在此处删除加载的资源,或删除粒子系统以防止内存泄漏等。 + +## Animation End + +在GameMaker Studio 2中,子画面不是静态的,因为它们可以具有子图像并以不同的速度进行动画处理。动画的每一帧(称为子图像)都有自己的编号,从0开始,可以通过代码或通过动作进行检查,但是有时您真正需要知道的是动画何时结束。就是在这个事件被触发时,恰好在动画的末尾,当子图像索引显示已到达最后一帧时。此事件对于许多事情确实非常有用,例如爆炸对象,您可以在其中设置实例以在到达动画的最后一帧时销毁自身。 + +## Animation Update + +此事件仅设计用于骨骼动画功能。这是一个特殊事件,在使用骨骼动画精灵的实例中的每个步骤都会触发该事件,该事件旨在在为当前动画状态计算骨骼的方向之后但在提交该数据之前“拦截”骨骼数据用于绘图。 这使您可以使用适当的功能对骨骼数据进行修改。 + +## Animation Event + +此事件仅设计用于骨骼动画功能。在已为其分配了骨骼动画精灵的对象中触发此事件,并且该对象正在使用分配的精灵的默认绘制,或者正在通过draw_self()绘制该精灵。当前,它不会以任何其他方式绘制的骨骼动画精灵上触发。该事件旨在捕获和传输已分配给Sprite动画中特定事件的数据,如用于制作事件的编辑器中所定义。 + +触发动画事件时,将创建一个特殊的DS映射,称为event_data映射(与我们为异步事件拥有async_load映射相同)。该映射将具有以下所有键/值对,您可以对其进行解析以获得有关事件的信息: + +- "**name**": 事件的名称(在动画程序中定义) +- "**track**": 播放动画的轨道的索引(默认为0) +- "**integer**": 与事件关联的整数值(在动画程序中定义,默认为0) +- "**float**": 与事件关联的浮点值(在动画程序中定义,默认为0.0) +- "**string**": 与事件关联的字符串值(在动画程序中定义,默认为空字符串”) + +## Path Ended + +路径可能是制作任何游戏的重要组成部分,无论是用于塔防类型的游戏,还是射击射击游戏或其他任何东西,并且当实例到达其末尾时执行某些特定动作通常非常重要小路。此事件将检测到此事件,并在发生该事件时触发该事件,使您可以添加代码或采取措施来应对这种情况,例如,在塔防游戏中,您可以使用此事件来破坏实例并从目标对象中删除生命值。 + +## User Events + +这些特殊事件不是由GameMaker Studio 2自动触发的,而是必须由您在游戏运行时使用代码或操作隐式调用。总共有12个用户事件,它们可以包含代码或动作,可以在游戏循环中的任何时间调用它们,这使它们对于创建自己决定的事件非常有用。 + +## Broadcast Message + +只要精灵或序列生成广播消息字符串,就会触发此事件。有关更多信息,请参见广播消息页面。 + + +``` + +`GameMaker/The Asset Editors/Objects/Object Variables.md`: + +```md +# Object Variables 对象变量 + +当您单击“变量”按钮时,它将打开“对象变量”窗口,然后您可以使用该窗口在对象的“创建事件”之前生成任意数量的变量: + +![Object Editor Variables Window](images/Object Variables/Editor_Object_Variables.png) + +在此窗口中,您可以单击添加按钮将新变量添加到列表中。然后,您可以命名变量以及设置其类型并调整其属性: + +![Object Editor New Variable](images/Object Variables/Editor_Object_NewVariable.png) + +变量名称必须以字母开头,并且只能包含字母,数字和下划线符号“ _”,最大长度为64个符号。因此,有效变量是 ` fish` ,` foo_bar` ,` num1` 之类的东西,无效变量将是` 6fish` ,` foo bar` 或` * num` 。给出变量的名称后,需要设置其类型,可以是以下任意一种: + +![Object Editor Variable Types](images/Object Variables/Editor_Object_VariableTypes.png) + +值得注意的是,此功能在处理父/子实例时特别有用-因为您可以使父对象具有一组定义的变量,然后只需在子对象中对其进行修改(请参见下面的“对象变量和子对象”部分更多详细信息)-以及在将实例添加到房间编辑器中时(请参见**Layers** > **Instance Layer**部分),因为您还可以为放置在房间中的单个实例修改这些变量。 + +下面列出了可以创建的不同类型的变量: + +- **Real**: 实数是不是整数的任何数字,可以是正数或负数。因此,124.5、45639.566546456、0.9,-45.5等都是实数的示例。所有实数都存储为64位浮点值(双精度)。实数(和整数)数据类型具有用于设置值范围的选项,可通过单击选项按钮![Variable Options Icon](images/Object Variables/Icon_VariableOptions.png)来访问这些值: + +![Object Editor Real Variable Options](images/Object Variables/Editor_Object_RealOptions.png) + +选中此选项后,您可以输入一个开始值和一个结束值,而不是在“对象变量”窗口中显示变量的固定值,而是将一个滑块固定在这些值上: + +![Object Editor Variable Range](images/Object Variables/Editor_Object_VariableRange.png) + +- **Integer**: 整数是整数,可以是正数或负数,例如30004、19、0,-300。请注意,您可以为输出整数设置一个值范围,如上面对实数所述。 +- **String**: 字符串是放在双引号中的任何内容,例如“ fish”,“ Hello World”或“ 12345”。 +- **Boolean**: 布尔值是true或false的值。在“对象编辑器变量”窗口中,它简单地显示为一个复选框,您选中了true,取消选中了false。 +- **Expression**: 表达式是一个数学短语,可以包含普通数字,变量,字符串或GML函数以及一个或多个运算符。例如`sqrt(85 * 6)+ 5.5`是一个表达式。 +- **Resource**: 资源只是您在资源浏览器中定义的任何一种资源。选择此资源类型后,可以单击打开资产浏览器按钮以打开资产浏览器![Open Asset Explorer Icon](images/Object Variables/Icon_OpenAssetExplorer.png)并选择所需的资源。 +- **List**: 选择输入的列表类型意味着您可以创建一系列值(可以是字符串,实数,表达式等),然后可以选择一个或多个值以返回变量。要定义列表中的项目,您需要先将它们添加到选项中(单击![Variable Options Icon](images/Object Variables/Icon_VariableOptions.png)按钮): + +![List Options](images/Object Variables/Editor_Object_ListOptions.png) + +您可以从下拉菜单的主窗口中选择该项目作为变量的默认值: + +![Object Editor List Menu](images/Object Variables/Editor_Object_ListType.png) + + 请注意,如果在“列表选项”中勾选了“多选”,则该变量将成为所有选定选项的数组: + +![List Array Options](images/Object Variables/Editor_Object_ListArray.png) + +- **Colour**: 颜色类型供您定义要存储在变量中的颜色值。您可以输入一个实数(从0到16777216,这将是一个alpha值255)或一个十六进制值(以$ RRGGBBAA的格式),或者您可以双击![LMB Icon](images/Object Variables/Icon_LMB.png)色样以打开拾色器并在那里定义颜色。 + +# Object Variables And Child Objects + +对象变量的一个重要功能是它们可以被资产浏览器中的任何子对象继承(有关更多信息,请参见“Parent Objects”部分),这意味着您可以选择覆盖或更改其中的任何一个或全部如果您愿意的话。 + +当您创建另一个为其定义了变量的对象资源的子对象时,这些对象将显示在“对象变量”窗口中,如下所示: + +![Object Editor Variable In A Child Object](images/Object Variables/Editor_Object_ChildVariables.png) + +您可以在顶部图像中看到Parent对象具有四个Object Variables,在底部图像中还显示了这些变量,仅“灰显”并且具有Inherited From Parent![Inherited From Parent Icon](images/Object Variables/Icon_InheritedVariable.png)图标,以表明它们已被继承。如果您单击覆盖变量![Override Variable Icon](images/Object Variables/Icon_OverrideVariable.png)按钮,则可以编辑这些继承的变量,因此您可以调整范围滑块或值,或者从列表中选择其他项目等。请注意,在编辑父变量时,您只能更改定义的值,而不能更改名称,也不能更改变量选项。还要注意,您也可以向子对象中添加新的对象变量(在上例图像中,您可以看到“字符类型”是仅用于子对象的新对象变量)。 + +如果您已编辑了继承的对象变量,则可以单击Delete![Delete Variable Icon](images/Object Variables/Icon_DeleteVariable.png)按钮以删除编辑,而不是变量。该变量仍将从父级继承,但会再次使用父级默认值。如果需要完全删除变量,则必须从父对象执行此操作。 +``` + +`GameMaker/The Asset Editors/Objects/Parent Objects.md`: + +```md +# Parent Objects 父对象 + +在GameMaker Studio 2 IDE中使用对象时,可以设置父级/子级层次结构。通过单击对象编辑器中的“Parent”按钮,然后从资源管理器中选择另一个对象作为要编辑的对象的“Parent”对象,可以完成此操作: + +![Adding A Parent](images/Parent Objects/Editor_Objects_Parent.png) + +因此,您的游戏项目中的每个对象都可以有一个父对象,但这意味着什么呢?好吧,当为对象分配父对象时,它可以与该父对象共享代码,操作和事件。这种共享称为“继承”,而具有父对象的对象称为“子对象”。子对象不仅可以与其父对象共享代码,还可以在父对象上进行检查和运行代码,并且它还将自动包含子代对象,从而节省了大量时间和精力。 + +如果这听起来很复杂,那么查看父对象的另一种方法是在同一伞下将对象“分组”在一起并使它们共享某些东西而又不会失去自己的唯一身份的方法。也许这仍然不能使事情澄清太多,所以让我们举一些例子... + +假设您有一个“玩家”对象和四个不同的“敌人”对象。现在,您希望玩家如果触摸到这四个对象中的任何一个,就会死亡,这通常会导致四个不同的碰撞事件,并产生四个不同的动作或代码集,每个敌人对象一个。但是,如果我们为所有敌人创建父对象,那么我们可以对父对象创建一个碰撞事件,并且无论四个“子”敌人对象中的哪一个接触玩家,它都会触发。方便的东西!在实际的GameMaker Studio 2对象中,您将看到以下内容: + +![Parenting For Collisions](images/Parent Objects/Editor_Object_ParentCollision.png) + +在左侧,我们有四个单独的碰撞事件,在右侧,我们有一个碰撞事件,因为我们创建了一个“父”对象并为其分配了所有敌人对象。请注意,父对象不必在其中包含任何事件或代码... + +继承的另一个示例是,如果您有一个游戏,想要创建10个不同外观的对象,并使它们的行为均相同。为此,您将创建一个父对象,并在必需的事件中包含所有行为操作或代码,然后您将创建十个对象,它们不包含任何动作或代码,但具有不同的Sprite,并为其分配父对象。现在,当您将这些实例放置在房间中时,它们的行为都相同,但是看起来却有所不同,因为它们将“继承”父对象的事件。 + +最后,您可以使用育儿功能来“混合和匹配”事件和行为。我们将用一个最后的例子来说明这一点。假设您想要两个怪物:一个怪物上下移动,另一个怪物左右移动,但是您还希望它们中的两个具有相同的生命值,向玩家射击并在碰撞时伤害玩家。在这种情况下,您可以看到几乎所有事件都应具有相同的动作,只有一个或两个控制运动。 因此,同样,我们可以使一个对象成为另一个对象的父对象,但是在这种情况下,我们还为子对象定义了某些事件。这些事件“覆盖”父事件,这意味着只要子对象的事件包含动作,就会执行这些事件,而不是执行父事件中包含的动作。如果您还想执行父事件,则可以使用函数event_inherited()或DnD™操作“调用父事件”来调用所谓的“继承”事件。 + +![Inheriting Events](images/Parent Objects/Editor_Object_InheritEvents.png) + +上方左侧是其中包含5个事件的父对象,而右侧则可以看到“子”对象。子对象中也有5个事件,但是其中两个覆盖从父对象继承的事件(Step和Draw事件),而其他三个则变灰,因为它们是从父对象继承的事件。在事件编辑器中,继承的事件还将在其旁边带有“父替代”图标: + +![Parent Override](images/Parent Objects/Editor_Object_ParentOverride.png) + +在已继承的事件上单击![LMB Icon](images/Parent Objects/Icon_LMB.png)时,它将打开代码编辑器以显示继承的父代码,但是您将无法编辑此代码,因为只能在父对象本身中对其进行编辑。您可以在任何父事件上单击鼠标右键![RMB Icon](images/Parent Objects/Icon_RMB.png)以打开以下选项菜单: + +![Parent Event RMB Menu](images/Parent Objects/Editor_Object_Parent_Menu.png) + +在这里,您可以选择两个“打开父事件”来检查代码,也可以选择“继承事件”或“替代事件”。如果选择继承,则代码编辑器将打开并已添加函数event_inherited()(如果使用DnD™,则为调用父事件操作)。现在,您在此事件中放置的所有其他代码以及父对象具有的代码都将运行。如果选择覆盖事件,则代码窗口也会打开,仅现在不会调用event_inherited()函数,因此将运行您在此处添加的任何内容,而不是运行父对象中的代码。 + + + +> 注意:在代码编辑器中,您可以通过单击鼠标右键![RMB Icon](images/Parent Objects/Icon_RMB.png)并从弹出菜单中选择``转到对象''来快速导航到父对象,或者(如果事件已被覆盖)您可以选择``打开继承的事件''以直接转到到其中包含父事件代码的代码编辑器。 + +每当您在代码中定位父对象时,该代码也将同样适用于父对象的“子代”。当在动作中指示必须将该动作应用于某个对象的实例时会发生这种情况,而在代码中使用with()语句时会发生这种情况。当您调用代码函数(例如instance_position(),instance_number()等)时,它也将如此工作,其中-如果您提供父对象,则父对象和子实例的所有实例都将包含在检查中。最后,当您也引用其他对象中的变量时,育儿也可以工作,例如在上面的怪物示例中,如果将敌人1的速度设置为10,那么敌人2的速度也将变为10,因为它是敌人1的子对象。 + +通常,在大多数情况下,创建一个基础父对象并让该基础对象包含所有默认行为,但在游戏中不要使用其默认实例,通常被认为是一种好习惯。而是使用所有子对象,并且仅在类似我上面概述的情况下使用父对象,发生冲突,引用变量等。 您还应该意识到父母也可以有父母!显然,您无法创建“父级1是父级2的子级是父级1的子级”的循环,但是您可以创建“对象层次结构”,其中“父级3是父级2的子级是父级1的子级”。这对于保持游戏的结构性非常有用,强烈建议您学习使用此机制。 + + +``` + +`GameMaker/The Asset Editors/Objects/Physics Objects.md`: + +```md +# Physics Objects 物理对象 + +首次创建新对象资源时,会出现一个标记为Uses Physics的复选框,默认情况下不会选中该复选框。选择此选项将在对象的实例放置在房间中时从根本上改变对象的行为,因为这会切换对象的物理属性,并意味着其“传统”运动和碰撞功能不再有效(但仅当它放置的房间也被标记为物理房间。请参阅``房间编辑器属性''部分。选中此选项后,对象资源窗口将为物理编辑器打开一个新的链接窗口: + +![Object Editor Physics](images/Physics Objects/Editor_Objects_Physics.png) + +在开始编辑物理行为的值之前,最好先设置碰撞形状。使用“正常”碰撞系统,您的碰撞基于分配给对象的精灵的蒙版,但是启用物理功能后,情况将不再如此。我们需要自己为对象分配一个碰撞形状(通常称为夹具,更多信息请参见物理功能),可以是您自己定义的圆形,矩形或多边形。单击“修改碰撞形状”按钮,将打开以下链接的窗口以编辑形状: + +![Object Editor Physics Shape Editor](images/Physics Objects/Editor_Objects_PhysicsShape.png) + +这看起来与“路径编辑器”相似,并且功能大致相同。但是,根据您选择的遮罩,它可能会更具限制性: + +- 如果有圆形蒙版,则只能拉出任何路径点以使圆直径更大或更小 +- 如果您有矩形遮罩,则可以移动四个角中的任何一个,但是相应的四个角将自动调整位置以始终保持矩形 +- 如果您具有自定义(多边形)蒙版,则该蒙版必须具有最少3个,最多8个点,并且必须是凸形的(请参见下图)。 + +![Object Editor Physics Shape Type](images/Physics Objects/Editor_Object_PhysicsShape.png) + +设置好形状后,您需要定义对象要具有的基本物理属性。通过更改以下参数进行配置: + +## Density 密度 + +事物的密度定义为每单位体积的质量,这基本上意味着有多少质量塞满了它所占据的空间。因此,球囊由于其质量小而占据较大的空间而具有低密度,而铅条由于其质量大而具有较高的密度而又仅占据很小的空间。在GameMaker Studio 2 2D物理世界中,将根据您输入的密度值以及为灯具定义的形状的表面积自动为您计算质量。这将直接影响固定装置的惯性以及其对碰撞和力的反应方式,因此,如果您制作的小形状具有高密度,它将具有非常大的质量(如铅条),但是如果您定义一个低密度的大形状,则其质量会小得多(例如气球)。 + +请注意,将密度设置为0将创建一个``静态''夹具,该夹具本质上是无限密集的,并且不受外力或碰撞的影响,除非在特殊情况下将实例创建为运动学实例(有关更多详细信息,请参见下文)。 + +## Restitution 还原 + +在物理学中,恢复被定义为“物体或系统在弹性变形后返回其原始状态”,但是由于GameMaker Studio 2中的固定装置实际上是刚性物体并且不能变形,恢复实际上是一种表示如何进行恢复的方式。固定装置的“弹性”。此设置将影响一个对象与其他对象碰撞时“反弹”的程度,并且共同依赖于作用在该实例上的其他力,例如重力和摩擦力。 + +## Collision Group 碰撞组 + + 默认情况下,所有物理对象的碰撞组为0,这意味着它们将正常交互,这意味着它们必须具有碰撞事件才能相互作用(简单的注释将触发碰撞),否则不会发生碰撞被注册。但是,您也可以指定一个对象应属于特定的编号碰撞组。 通过将对象分配给正碰撞组(即,三个对象都在“ 2”组中),您就是在告诉GameMaker Studio 2,这些对象的实例应始终发生碰撞,即使它们在碰撞事件中没有任何作用,也要遵守物理属性您已经分配了他们以及他们所在的房间。相反,如果您的对象具有碰撞组为负的对象(即:四个对象被分配为“ -1”),您将告诉GameMaker Studio 2,它们永远不应碰撞,并且它们之间的任何碰撞事件都将被忽略。 + +> *注意:使用碰撞组会极大地增加物理系统所需的处理,并且仅在绝对必要时才应使用,并且应尽可能少地使用碰撞组。* + +## Linear Damping 线性阻尼 + +阻尼用于降低实例在房间中移动时的物理世界速度。 原则上,这就像“真实”世界中与表面的摩擦,但是在GameMaker Studio 2物理系统中,摩擦实际上仅在两个具有碰撞形状(夹具)的实例接触时才应用。因此,您可以将线性阻尼视为更像是``空气摩擦'',它会随着时间的流逝逐渐降低任何移动的物理实例的速度,而无需与任何其他物理实例接触。 + +## Angular Damping 有角度的阻尼 + +如果您考虑``现实世界''中的任何旋转物体,除非它具有马达或在太空中,否则其旋转会由于外力的影响(例如机械摩擦或与周围空气或水的摩擦)而随着时间的流逝而减速它)。我们可以使用角度阻尼选项来模拟此效果并降低物理世界中实例的旋转速度,因为没有它,任何旋转的实例将继续无限旋转。 + +## Friction 摩擦力 + +摩擦力是抵抗材料元素彼此相对滑动的力,在GameMaker Studio 2物理世界中,摩擦力是由两个实例与物理夹具碰撞引起的动量损失。因此,当两个实例发生碰撞时,它们的运动会受到该值的影响,而高摩擦将导致动量损失大于低值。 + + + +最后,您可以将灯具的三个额外选项标记为“打开”或“关闭”:“传感器”,“开始唤醒”和“运动”。这些标志中的每一个都将通过以下方式更改灯具的行为: + + + +## Sensor 传感器 + +通过选中此框,您可以告诉GameMaker Studio 2基本“忽略”对象的物理属性,但仍返回与周围对象的碰撞事件。这样,您可以拥有一个在房间中没有物理存在的实例,但是它可以对另一个实例(例如玩家)的碰撞做出反应,并依次执行某些操作(例如打开一扇门)或在另一个实例中触发某种动作放在房间里。 + +> 注意:仅在首次发生碰撞时,传感器固定装置才会触发碰撞事件,这意味着当两个物体继续重叠时(传统情况下会发生这种情况),您不会收到碰撞事件流。如果它们停止重叠并随后重叠,则会触发另一个碰撞事件。 + +## Start Awake 保持唤醒 + +默认情况下会对此进行检查,并表示其在放置房间的开始时的初始“状态”。通常,您希望它从一开始就醒着(即:参与物理世界),但是有时由于突然开始物理模拟,可能会导致某些不稳定性和不良影响。 为了避免这些影响,您可以取消选中此标志,并且实例将在灯具处于睡眠状态时创建,并且除非有其他作用,否则它将不参与物理仿真。 + +请注意,当物理实例不再处于任何运动状态或与任何其他物理实例发生交互时,它们将被标记为“睡眠”,并且“睡眠”实例只是物理仿真当前尚未完全处理的实例(节省CPU开销)。 + +## Kinematic 运动学上的 + +在基于物理学的游戏中,某些情况下您想四处走动,但又不想受到重力等力的作用,也不想与动态物体碰撞而产生的力(例如,在平台游戏中移动平台) )。 对于此类对象,只需将固定装置的密度设置为0,就意味着物理学将假定该对象是静态的,并且对任何事物都不会产生任何反应。但是,选中此框将使静态对象运动,尽管它仍然不受碰撞和重力等影响,但现在可以使用适当的变量来移动或旋转它(例如,请参见phy_position_x和phy_position_y)。 + + +``` + +`GameMaker/The Asset Editors/Paths.md`: + +```md +# The Path Editor 路径编辑器 + +![The Path Editor](images/Paths/Editor_Paths.png) + +在创建游戏时的某个时候,您可能需要一个实例来遵循通过关卡的路径。现在,只需在房间内创建一个位置数组,然后在它们之间移动一个实例,就可以用代码完成此操作,但这可能很麻烦,并且很难更改,测试和适应不同的实例。因此,GameMaker Studio 2具有路径资源和专用的路径编辑器。 + +基本思想非常简单-您可以通过在路径编辑器中绘制路径来定义路径,然后可以在对象的创建事件中放置一个动作(或代码),以告知实例遵循该特定路径。您还可以设置跟随路径的速度以及与房间内路径的位置和方向有关的许多其他操作。首次创建路径资源时,将打开路径编辑器窗口,其中包含以下选项: + + + +## Path Properties 路径属性 + +与GameMaker Studio 2中的所有资源一样,您应该给您的新路径一个唯一的名称,因为这将在您制作的整个游戏中用来标识它,并且很多人都在使用一个好的系统作为前缀(或后缀) ),以便在代码或操作中轻松识别的资源(例如:path_Enemy_1或PowerUp_path)。 + +命名路径后,可以通过在右侧的主编辑器中放置点来开始定义路径,每个点以及其位置和速度都将添加到列表中。路径点的速度值由使实例沿路径移动的速度的百分比定义(默认值为100%)。因此,如果沿路径以2的速度(每步2像素)启动实例,然后路径点之一的速度值为50%,则当实例接近该点时其速度将降低为1 (路径速度的50%)。同样,您可以将速度设置为150%,实例将在达到该点时加速。这样,您无需任何代码就可以创建一些动感十足的动态外观。 + +值得注意的是,您可以在列表中的任何定义点上快速双击![LMB Icon](images/Paths/Icon_LMB.png)以将编辑器平移到所选点,或在其中一个值上缓慢双击![LMB Icon](images/Paths/Icon_LMB.png)以手动编辑它(x,y或速度)。 + +您还可以指定整个路径的连接类型以及是否应关闭路径(闭合路径会将路径上的最后一个点与第一个点连接起来)。连接类型可以是直线,也可以是平滑的,其中直线连接只是简单地以线性为基础连接路径的各个点,每个点的角度都发生变化,而平滑的连接将创建一条柔软得多的路径,该路径可能不会直接通过通过任何点,而是走一条近似线,使用平滑插值创建漂亮的曲线。您还可以指定曲线的精度(默认值为4),精度可以是1到8,其中8是最平滑的。下图显示了这些选项如何影响路径的示例: + +![Path Editor Examples](images/Paths/Editor_Paths_Examples.png) + +## Path Editing + +您可以在编辑窗口中的任意位置单击![LMB Icon](images/Paths/Icon_LMB.png)以将点添加到路径中,所有后续单击将添加更多点。放置到编辑器中后,可以通过单击![LMB Icon](images/Paths/Icon_LMB.png)并拖动它或通过更改左侧显示的X和Y位置和速度的值来移动点(请参见上面的路径属性)。您可以使用![CTRL Icon](images/Paths/Icon_Ctrl.png)/![CMD Icon](images/Paths/Icon_Cmd.png)/并在多个点上单击鼠标左键![LMB Icon](images/Paths/Icon_LMB.png)以将它们一起选择,然后可以根据需要将这组点拖动并放置。您也可以按删除所选的一个或多个点。 + +右键单击![RMB Icon](images/Paths/Icon_RMB.png)时,在编辑器窗口中有一个附加菜单供您使用,如下所示: + +![Path Editor RMB Menu](images/Paths/Editor_Paths_Menu.png) + +该菜单中的每个项目都如下工作: + +- **Delete Points** - 删除所选点。 +- **Pan To Path** - 平移到路径-将编辑器中的视图平移到路径的中心。 +- **Reverse** - 反转-反转路径点的顺序。这不会移动它们,而是会更改处理它们的顺序,以使起点成为终点,反之亦然。 +- **Flip** - 翻转-沿水平轴翻转路径。 +- **Mirror** - 镜像-沿垂直轴翻转路径。 + + + +## The Toolbox 工具箱 + +您可以在顶部的工具箱按钮中控制在编辑器窗口中绘制事物的方式。您可以打开或关闭背景网格,以及将路径点的添加设置为是否捕捉到该网格: + +![Path Editor Grid Options](images/Paths/Editor_Paths_GridOptions.png) + +捕捉值指示网格单元的大小,可以将其更改为所需的任何值。其余工具专用于放大和缩小以及平移编辑器区域,您可以单击![Zoom Reset Icon](images/Paths/Icon_ZoomReset.png)按钮将显示重置为1:1。请注意,如果您平移了您想要绘制路径的位置,则可以使用鼠标右键菜单,然后单击![RMB Icon](images/Paths/Icon_RMB.png)`Pan to Path`以滚动回到工作位置。 + +## Path Points + +路径只是一条线连接在一起的点的集合。在编辑器中,使用鼠标左键![LMB Icon](images/Paths/Icon_LMB.png)放置这些点。您可以在房间编辑器中的任何位置添加点,它们将连续添加到点列表中以构成路径。如果将点放置在连接线或另一个节点附近,则新点将插入到路径中而不是添加到路径中(您可以在“首选项”中为此设置检测距离)。您可以按![CTRL Icon](images/Paths/Icon_Ctrl.png)/![CMD Icon](images/Paths/Icon_Cmd.png)并在已放置的点上单击![LMB Icon](images/Paths/Icon_LMB.png)以选择它们,然后对路径点执行的任何操作(如移动它们)将作为一组应用于所有选定点。 要删除点或选择点,您可以右键单击![RMB Icon](https://raw.githubusercontent.com/supermanc88/ImageSources/master/Icon_RMB.png)并选择`Delete Poin`或按![Delete Key Icon](images/Paths/Icon_Delete.png)键。 + +请注意,您还可以使用以下快捷方式执行一些额外的操作,这些操作无法通过选择路径点上的鼠标右键菜单进行: + +- T + ![LMB Icon](images/Paths/Icon_LMB.png): 平移(移动)路径点 +- R + ![LMB Icon](images/Paths/Icon_LMB.png): 旋转路径点 +- S + ![LMB Icon](images/Paths/Icon_LMB.png): 缩放路径点 +``` + +`GameMaker/The Asset Editors/Rooms/Layer Properties.md`: + +```md +# Layer Types And Properties 图层类型和属性 + +您在“Room Editor”中创建的房间中的所有内容都放置在一个图层上。可以从“Layers Editor”中添加,删除和排序图层,并且可以使用各种不同的类型,每种类型都有其自己的一组选项和工作方式。您不受限于每种类型的层数,并且可以具有多个图块层,路径层或实例层等。每种类型的图层都有其自己的属性窗口,您可以在其中根据图层类型设置不同的内容。您还可以切换图层的可见性,在运行时不处理不可见的图层(但是您可以随时使用适当的图层功能使它们再次可见)。 + +![Room Layers](images/Layer Properties/Editor_Room_Layers.png) + +上图显示了层编辑器,当前层在顶部列出,而用于创建层的不同按钮在底部列出。您可以通过缓慢双击名称来从此编辑器重命名任何图层(请注意,除了字母数字和下划线“ _”字符以外,您不能使用其他任何名称),还可以通过单击眼睛图标![Eye Icon](images/Layer Properties/Icon_Eye.png)。如果房间已从父房间继承了图层,则可以从底部的按钮切换继承,但是请注意,这将影响所有图层(有关继承的更多信息,请参见[here](https://manual-en.yoyogames.com/The_Asset_Editors/Room_Properties/Room_Inheritance.htm) )。您还可以使用![Shift Icon](images/Layer Properties/Icon_Shift.png)或![Control Icon](images/Layer Properties/Icon_Ctrl.png)/![CMD Icon](images/Layer Properties/Icon_Cmd.png)并在多层上单击鼠标左键![LMB Icon](images/Layer Properties/Icon_LMB.png)以选择要复制或删除的图层(这些选项也可从右键菜单中获得)。 + +为了帮助排序图层并使它们保持整洁,您可以通过单击文件夹图标![Folder Icon](images/Layer Properties/Icon_Folder.png)在图层文件夹中创建图层组。您也可以通过单击删除按钮![Delete Layer Icon](images/Layer Properties/Icon_DeleteLayer.png)来删除一个或多个选定的图层。所有图层都有一个深度值,该值定义了在房间中渲染其内容时该图层在绘制顺序中的放置位置。 从最高深度到最低深度绘制图层,因此将在深度为200的图层上绘制深度为-100的图层。 + +> 注意:最小和最大层深度为-16000至16000。尽管所有事件对于该层上的任何内容仍将正常运行,但不会绘制该范围之外的任何内容。 + +如果在任何图层上单击鼠标右键![RMB Icon](images/Layer Properties/Icon_RMB.png),将显示图层菜单: + +![Room Layer Menu](images/Layer Properties/Editor_Room_LayerSubMenu.png) + +在这里,您可以打开所选图层的图层属性窗口,删除图层,重命名图层或添加子图层。如果选择添加子层,则当前层将收到一个“文件夹”图标,以显示有附加的子层,并且将在其下创建新层,并在右边显示。然后,您可以选择让子层从父层继承其属性,还可以设置是否应从父层继承可见性。 + +请注意,您可以在窗口中上下拖动图层以对其进行重新排序,也可以使用![Shift Icon](images/Layer Properties/Icon_Shift.png)+![LMB Icon](images/Layer Properties/Icon_LMB.png)选择从一个图层到另一个图层(包括中间的所有图层)或使用![Control Icon](images/Layer Properties/Icon_Ctrl.png)/![CMD Icon](images/Layer Properties/Icon_Cmd.png) +![LMB Icon](images/Layer Properties/Icon_LMB.png)来选择和移动多个图层。一次一个。 如果将图层放置在图层文件夹的顶部,它们将被移动并设置为放置它们的文件夹的子图层。您还可以锁定图层,以便使用锁定按钮![Padlock Icon](images/Layer Properties/Icon_Padlock.png)不会错误地编辑它们。 + +> *注意:在实际的编辑器窗口中,您可以将不同的资产放置在它们的层上,您可以在任意位置按住'P'+来立即选择该资产并跳到放置该资产的层。* + +您可以在下面找到每种可用图层类型的概述以及该图层的可编辑属性: + +## ![Background Layer Icon](images/Layer Properties/Icon_Background.png)Backgrounds 背景 + +您可以通过单击按钮![Background Layer Icon](images/Layer Properties/Icon_Background.png)添加新的背景层。这将添加新层,然后打开背景属性窗口: + +![Room Layer Background properties](images/Layer Properties/Editor_Room_BackgroundProperties.png) + +创建新的背景层时,默认情况下该层为空,因此将绘制定义的背景色。如果未为背景图层图像分配精灵,则将改为绘制此图片,并且您可以单击颜色框以打开颜色选择器并更改使用的颜色。如果确实选择了精灵,则可以勾选平铺选项以将其水平或垂直平铺,或使其拉伸图像以填充整个房间。 + + 如果您想让背景朝某个方向滚动,您也可以设置水平和/或垂直速度(每个游戏步骤,它都会沿给定的访问方向移动您在此处设置的像素数),并且您还可以重新设置通过设置x和y偏移值来定位背景图像。如果您选择的Sprite资源具有多个子图像,您也可以设置背景的动画速度,尽管默认情况下该资源被锁定为您在Sprite Editor中为所使用图像设置的值。最后,您可以显式设置图层的深度,尽管默认情况下仍被锁定,并且您可以简单地在“图层编辑器”中拖动并重新排列图层顺序以更改此值。 + +请注意,您也可以通过单击最顶部的按钮来从此窗口切换背景的继承。 + +## ![Instance Layer Icon](images/Layer Properties/Icon_Instance.png)Instances 实例图层 + +您可以通过点击按钮![Instance Layer Icon](images/Layer Properties/Icon_Instance.png)添加一个新的实例层。这将添加新层,然后打开实例属性窗口: + +![Room Layer Instance properties](images/Layer Properties/Editor_Room_Instances.png) + +该窗口仅显示该层上当前所有实例的列表。您可以通过打开资源浏览器然后将对象从其中拖到房间中来向图层添加更多实例,或者您可以从资产浏览器中选择一个实例,然后按住![Alt Icon](images/Layer Properties/Icon_Alt.png)以在原地预览资源,如果您另外单击鼠标左键![LMB Icon](images/Layer Properties/Icon_LMB.png),则可以添加实例,甚至可以通过按住按钮并移动鼠标来将其“绘制”到房间层中。 + +可以通过单击![LMB Icon](images/Layer Properties/Icon_LMB.png)并将列表中的实例拖动到所需位置来对图层列表进行排序,但是值得注意的是,这纯粹是出于房间编辑器的组织目的,如果要设置特定的创建顺序,请使用创建订单窗口,可以从“房间属性”窗口或“房间”上下文菜单中打开。 + +在房间编辑器本身或属性列表中的任何实例上双击都会打开一个新窗口,让您修改单个实例的各个方面: + +![Room Instance properties](images/Layer Properties/Editor_Room_InstanceProperties.png) + +默认情况下,该实例的名称类似于'inst_38B0BFF',但是可以将其更改为所需的任何名称,只要该名称仅使用字母数字字符和下划线'_'(且不包含空格)即可。但是,值得注意的是,此处给出的名称(无论是您提供的名称,还是IDE提供的默认名称)都被认为是常量,因此实例必须唯一。这也意味着它可以在您的游戏代码中用于标识实例,但前提是您与实例位于同一房间。 + +在实例名称下方,您可以使用按钮设置该实例是否要从父空间继承,更改您希望其成为实例的对象,或者打开对象编辑器(如果您要在其中进行任何更改)。有一个按钮可通过“对象变量”窗口检查和编辑变量,您也可以更改此实例的初始特征,设置颜色以使其与之混合(默认为白色),或者旋转和翻转它。您还可以沿任一轴或两个轴缩放实例,并设置其在房间内的位置,以及图像速度和供精灵使用的初始帧。 + +最后,您可以为实例提供一些创建代码,该代码将在对象创建事件代码之后直接运行,并且对于该实例是唯一的(因此,您可以在对象中设置一个变量,所有实例将在创建该对象时对其进行修改代码,以便它是一个实例的唯一值)。单击此选项会将脚本窗口链接到实例,您可以在其中添加任何功能或修改任何变量,并且在创建实例并运行实例Create Event之后将直接运行此代码。请注意,您可以独立于实例继承打开或关闭创建代码的继承。 + +值得注意的是,制作游戏时,图层上的每个实例都会自动标记为导出。但是,尤其是在处理继承时,可能是您不想在最终游戏中将特定实例添加到房间中(或者您可能在每个房间中都有一个调试实例,只是想将其从最终游戏中排除) )。如果是这种情况,则只需取消选中列表中实例左侧的“导出”复选框,即可防止将其导出为可执行文件的一部分。但是,请务必注意,如果您有任何引用实例的代码,则游戏将无法正常运行,因此请谨慎使用此功能。 + +## ![Tile map Layer Icon](images/Layer Properties/Icon_Tilemap.png)Tile Maps 图块 + +你可以通过点击按钮![Tile map Layer Icon](images/Layer Properties/Icon_Tilemap.png)添加一个新的平铺地图层。这将添加新图层,然后打开`Tile Map Layer Properties `窗口和`Tile Editing`窗口: + +![Room Layer Tile properties](images/Layer Properties/Editor_Room_TileEditor.png) + +如果您希望图层网格与(0,0)不完全对齐,则可以在``平铺地图图层属性''中切换图层的继承,更改正在使用的平铺集并设置图层的x或y偏移量房间的位置。您还可以设置渲染层的深度(具有其自己的唯一继承按钮),也可以“锁定”该深度,这将防止在创建之后进行任何进一步的编辑。 + +图层属性窗口打开时,还将打开“平铺编辑”窗口,其中显示了正在使用的平铺集(如果错误关闭此窗口,则可以从IDE顶部的**Room** > Tile Editing 菜单中再次将其打开) 。您可以在任意图块上单击以将其选择为当前画笔,然后通过在房间画布上单击![LMB Icon](images/Layer Properties/Icon_LMB.png)来将它们绘制到房间编辑器中的图层上;或者,如果要一起绘制多个图块,可以通过单击将它们作为一个组进行选择鼠标左键![LMB Icon](images/Layer Properties/Icon_LMB.png)并拖动以在绘制之前从图块集中选择它们。如果要绘制的图块范围不是一个块,则可以按住![Control Icon](images/Layer Properties/Icon_Ctrl.png)/![CMD Icon](images/Layer Properties/Icon_Cmd.png)然后使用鼠标左键![LMB Icon](images/Layer Properties/Icon_LMB.png)选择单个图块并以此方式创建自定义画笔(也可以从自定义画笔中删除图块使用 ![Alt Icon](images/Layer Properties/Icon_Alt.png) + ![LMB Icon](images/Layer Properties/Icon_LMB.png))。请注意,以这种方式创建自定义笔刷是临时的,并且设计为一次性使用,但是您可以使用``笔刷''选项卡创建永久笔刷,该笔刷将与图块地图图层一起存储。 + +请注意,``图块编辑''窗口允许您使用鼠标滚轮或顶部的按钮来放大和缩小图块,并且可以使用鼠标中键或 ![Space Icon](images/Layer Properties/Icon_Space.png) + ![LMB Icon](images/Layer Properties/Icon_LMB.png)平移。请注意,也有一个网格选项可以打开或关闭以界定窗口中的图块,并且您也可以设置网格的颜色,尽管无论是否可见图块都将始终对齐到网格。 + +“图块编辑”窗口还允许您从“库”选项卡中选择“笔刷”,“动画图块”和“自动图块”,所有这些都在“图块集编辑器”部分中进行了介绍。 + +在房间编辑器的顶部,选择了瓷砖图层后,您将看到一组特定于瓷砖的工具: + +| ![Pencil Tool Icon](images/Layer Properties/Icon_Tile_Draw.png)**Pencil [ 铅笔]** | 这是铅笔工具. 它使用选定的图块在``房间编辑器''中用鼠标左键![RMB Icon](images/Layer Properties/Icon_RMB.png)进行绘制,您可以用鼠标右键进行擦除。请注意,图块的索引值显示在“房间编辑器”的“房间状态栏”的底部,如果您选择了多个图块进行绘画(即画笔),则会显示所有使用的图块索引的列表。 | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| ![Eraser Tool Icon](images/Layer Properties/Icon_Tile_Erase.png)**Eraser [ 橡皮]** | 使用橡皮擦工具,您可以使用鼠标左键![LMB Icon](images/Layer Properties/Icon_LMB.png)在房间编辑器中擦除给定的瓷砖。本质上,所有这些操作都是将图块索引设置为0,这是保留的“空”图块。 | +| ![Fill Tool Icon](images/Layer Properties/Icon_Tile_Fill.png)**Fill [ 充满]** | 填充工具允许您使用当前的图块画笔来填充整个图块地图图层或某些图块。您使用的笔刷可以是单个图块,也可以是由多个图块组成的复合笔刷,甚至可以是动画库中的一个图块,然后在图层上单击![LMB Icon](images/Layer Properties/Icon_LMB.png)将使用所选笔刷填充这些图块。如果未使用选择工具,则填充将在所有连续的空图块或同一索引的所有连续的图块上进行。如果您在图层中有一个选择,则填充将被限制在该选择之内。 | +| ![Line Tool Icon](images/Layer Properties/Icon_Tile_Line.png)**Line [ 线]** | 选择`Line Tool`时,单击![LMB Icon](images/Layer Properties/Icon_LMB.png)然后拖动并释放将在您单击的位置和释放的位置之间创建一条平铺线。如果使用“选择工具”选择了瓷砖地图图层的某个区域,则仅在选择范围内添加线。 可以使用库中的单个图块笔刷,各种图块的复合笔刷或动画图块绘制线条。 | +| ![Rctangle Tool Icon](images/Layer Properties/Icon_Tile_Rectangle.png)**Rectangle [ 长方形]** | 选择矩形工具后,单击![LMB Icon](images/Layer Properties/Icon_LMB.png)然后拖动并释放将创建一个填充的矩形矩形,其中左上角是单击位置,右下角是释放位置。如果使用“剖面工具”选择了tilemap图层的某个区域,则矩形将仅添加到选择范围内。可以使用单个图块画笔,各种图块的复合画笔或库中的动画图块绘制矩形。 | +| ![Selection Tool Icon](images/Layer Properties/Icon_TileSelect.png)**Selection [ 选拔]** | 这是选择工具,可用于定义要处理的图块层的区域。 您可以单击鼠标左键![LMB Icon](images/Layer Properties/Icon_LMB.png),然后拖动鼠标以创建一个矩形区域,或者可以按/![Control Icon](images/Layer Properties/Icon_Ctrl.png)/![CMD Icon](images/Layer Properties/Icon_Cmd.png) +![LMB Icon](images/Layer Properties/Icon_LMB.png)添加多个选择,并按![Alt Icon](images/Layer Properties/Icon_Alt.png) + ![LMB Icon](images/Layer Properties/Icon_LMB.png)清除选择的一部分。要清除整个选择,可以按![Escape Icon](images/Layer Properties/Icon_Escape.png)。当您选择了图块图层的区域时,其余工具(“铅笔”,“翻转”,“旋转”等)将仅在所选区域内起作用。请注意,您还可以在房间编辑器中复制(![Control Icon](images/Layer Properties/Icon_Ctrl.png) / ![CMD Icon](images/Layer Properties/Icon_Cmd.png)+'C'),剪切(![Control Icon](images/Layer Properties/Icon_Ctrl.png) / ![CMD Icon](images/Layer Properties/Icon_Cmd.png)+'X')和粘贴(![Control Icon](images/Layer Properties/Icon_Ctrl.png) / ![CMD Icon](images/Layer Properties/Icon_Cmd.png)+'V'),然后将工具切换到铅笔并允许您用选定的瓷砖绘制房间图层。 | +| ![Autotile Tool Icon](images/Layer Properties/Icon_Tile_Auto.png)**AutoTile [ 自动平铺]** | 单击此工具将启用“自动平铺”绘画样式。启用此选项后,您可以从“自动拼板库”选项卡中选择任何图块,然后将其绘制到房间图层中,只要您正确设置了“自动图块”选项卡,GameMaker Studio 2就会自动更改它以匹配周围的图块。 请注意,从图块集中选择不属于autotile库的图块,会将绘图工具重置为标准“铅笔”工具。 | +| ![Inherit Tool Icon](images/Layer Properties/Icon_Tile_Inherit.png)**Inheritance [ 遗产]** | 使用此工具,您可以启用/禁用给定层上特定图块的继承。为此,房间必须首先是子房间,并且图层必须在图层属性中启用继承。 在这种状态下,房间编辑器默认会将所有继承的图块着色为红色,然后单击鼠标右键![RMB Icon](images/Layer Properties/Icon_RMB.png)将关闭该图块的继承,然后单击鼠标左键![LMB Icon](images/Layer Properties/Icon_LMB.png)将其重新打开。这样,您可以重复使用“基础”房间中的元素,而仅在继承所有其余部分的同时选择性地更改所需的图块。 | +| ![Flip Tool Icon](images/Layer Properties/Icon_Tile_Flip.png)**Flip [ 翻动]** | 用鼠标左键单击![LMB Icon](images/Layer Properties/Icon_LMB.png)``翻转''工具(或使用键盘快捷键'X')将沿水平轴翻转当前选择用于绘制的图块,而无需更改绘制工具(如果您选择了自定义画笔来绘制,则整个画笔会翻转)。如果您没有选择要绘制的图块,而是在图层上选择了一组图块,则翻转工具将翻转选定的图块。 | +| ![Mirror Tool Icon](images/Layer Properties/Icon_Tile_Mirror.png)**Mirror [ 镜子]** | 用鼠标左键![LMB Icon](images/Layer Properties/Icon_LMB.png)单击Mirror工具(或使用键盘快捷键'Y')将沿垂直轴镜像当前选择用于绘制的图块,而无需更改绘制工具(如果您选择了自定义画笔来绘制,则整个画笔将镜像)。 如果您没有选择要绘制的图块,而是在图层上选择了一组图块,则“镜像”工具将镜像选定的图块。 | +| ![Rotate Tool Icon](images/Layer Properties/Icon_Tile_Rotate.png)**Rotate [ 旋转]** | 用鼠标左键![LMB Icon](images/Layer Properties/Icon_LMB.png)单击旋转工具(或使用键盘快捷键'Z')将顺时针方向将当前选择用于绘制的图块旋转90°,而无需更改绘制工具(如果您选择了自定义画笔来绘制,整个画笔将旋转)。如果您没有选择要绘制的图块,而是在图层上选择了一组图块,则“旋转”工具将旋转选定的图块。 | + +请注意,如果您使用``选择工具''![Sekection Tool Icon](images/Layer Properties/Icon_TileSelect.png)在房间编辑器画布中选择一组瓷砖,然后剪切或复制它们,则它们将暂时成为当前绘画用的画笔。 + + + +## ![Path Layer Icon](images/Layer Properties/Icon_Paths.png) Paths + +您可以通过单击![Path Layer Icon](images/Layer Properties/Icon_Paths.png)按钮添加新的路径层。这将添加新层,然后打开路径属性窗口: + +![Room Layer Path properties](images/Layer Properties/Editor_Room_PathProperties.png) + +在这里,您可以从资产浏览器中选择要用于图层的路径,然后更改定义的连接类型(使用平滑插值或直线),以及是否关闭该路径以及创建路径的精度。还要注意,您可以在此处切换继承,并且可以将路径设置为特定颜色(默认为红色),以使在编辑器中更好地看到它们。如果没有定义路径资源,则可以单击“新建”按钮来创建路径资源。路径层也具有与之关联的深度值(默认情况下为锁定),即使使用它们时游戏中实际上并未渲染任何东西。 + +值得注意的是,您还可以通过单击鼠标左键![LMB Icon](images/Layer Properties/Icon_LMB.png)在路径中添加点,以及使用鼠标右键![RMB Icon](images/Layer Properties/Icon_RMB.png)删除点,或通过在其上拖动并拖动来选择一个点,所有这些都在实际的房间编辑器中完成,使创建和编辑路径非常容易(尽管您仍然可以照常在路径编辑器中编辑路径)。如果在不同的路径点上按住 ![Control Icon](images/Layer Properties/Icon_Ctrl.png) / ![CMD Icon](images/Layer Properties/Icon_Cmd.png) + ![LMB Icon](images/Layer Properties/Icon_LMB.png),则将全部选中它们,您可以将它们一起移动或从菜单选项中将其全部删除(如下所述)。 + +如果在操作路径时在房间编辑器窗口中单击鼠标右键![RMB Icon](images/Layer Properties/Icon_RMB.png),将获得以下选项: + +![Room Layer Path Menu](images/Layer Properties/Editor_Room_PathRMBMenu.png) + +在这里,您可以反转路径方向(这几乎看不到效果,但是可以反转计算路径点的顺序,因此起点变成终点,反之亦然),也可以水平或垂直翻转它们。您也可以从此菜单中删除选定的点。请注意,您可以按住'T','S'或'R'然后单击并拖动以平移,缩放或旋转整个路径。 + +## ![Asset Layer Icon](images/Layer Properties/Icon_Assets.png) Assets + +您可以通过点击按钮![Asset Layer Icon](images/Layer Properties/Icon_Assets.png)添加一个新的资产层。这将添加新层,然后打开资产层属性窗口: + +![Room Layer Asset properties](images/Layer Properties/Editor_Room_LayerAssetProperties.png) + +资产层只是一个独立于实例而已放置到房间中的图形资产(子图形或序列),您可以通过从资产浏览器中将它们拖入或在资产浏览器中选择所需的一个来放置它们。然后使用 ![Alt Icon](images/Layer Properties/Icon_Alt.png) + ![LMB Icon](images/Layer Properties/Icon_LMB.png) 对其进行“绘制”。Sprite资产类似于tile,只有它们可以完全动画化-如果所使用的sprite具有子图像,则将显示它们-没有针对tile的适当限制,即:它们不被强制网格化并且动画可以任何长度和速度。序列是进入房间时将播放的精灵,声音和实例的“偏转”。 + +将资产放置在资产层上时,它们将捕捉到在网格的房间编辑器顶部设置的网格捕捉值,但是如果要徒手放置它们,只需关闭网格或使用键![Control Icon](images/Layer Properties/Icon_Ctrl.png) / ![CMD Icon](images/Layer Properties/Icon_Cmd.png) 和鼠标左键按钮![LMB Icon](images/Layer Properties/Icon_LMB.png) 。在按住这些键的同时,您可以自由地将资产放置在任何地方,而不必依赖于网格分辨率。 + +将资产放置在房间中后,您可以双击它以打开资产属性窗口,从而对其外观有了一定程度的控制: + +![Room Asset properties](images/Layer Properties/Editor_Room_AssetProperties.png) + +在这里,您可以给资产指定一个唯一的名称,设置是继承自父房间,还是更改想要显示的精灵或序列。对于连续镜头,您可以设置动画速度和初始播放头帧以及要与之混合的颜色(默认为白色)和房间中的位置。您也可以更改Sprite资产的特征,设置颜色使其与之混合(默认为白色),或旋转和翻转它。您还可以沿一个或两个轴缩放精灵,并设置它们在房间中的位置。如果子画面具有子图像,则可以选择动画速度以及设置要显示的初始帧。 + +值得注意的是,制作游戏时,层上的每个资产都会自动标记为导出。 但是,尤其是在处理继承时,可能是您不希望在最终游戏中将特定的精灵添加到房间中。如果是这种情况,则只需取消选中图层属性列表中子画面左侧的“导出”复选框,即可防止将其导出为可执行文件的一部分。但是,请务必注意,如果您有引用该资产的任何代码,则游戏将无法正确运行,因此请谨慎使用此功能。 +``` + +`GameMaker/The Asset Editors/Rooms/Room Inheritance.md`: + +```md +# Room Inheritance 房间继承 + +房间编辑器的一个重要功能是继承。继承意味着您可以创建一个“父”房间,然后从中创建一个“子”房间,该子房间将继承所有父房间的属性。此子房间实质上是父房间的克隆,其中的所有内容都链接到父房间。这意味着,如果您在父视图中更改视图,移动图块或添加实例,它将反映在子房间中。不仅如此,在子房间中,您可以有选择地关闭特定层,实例,资产或其他任何所需的继承。 + +具有继承属性的房间将使继承按钮亮起-如下图所示-然后您可以使用它们来为给定的图层或资源等打开或关闭继承。 + +![Room Inherit Buttons](images/Room Inheritance/Editor_Room_Inheritance.png) + +乍一看这可能会有点困难,所以让我们看一个例子:假设您有一间房间,中间有一座城市和一座城堡。城市和城堡是使用不同的图块图层绘制的,并且您还具有实例和一些NPC可以走动的路径。您想要拥有它,以便在游戏中城堡被摧毁,但城市中的其他一切看起来和行为都一样。您将创建具有所有常规功能的初始房间,然后创建一个继承父房间层的子房间。在子房间中,您将关闭城堡区域的图块层(甚至单个图块)的继承,它们将移动/添加/删除图块以创建被破坏的城堡外观。当玩家进入该房间时,父房间中的所有实例以及所有瓷砖等都将保持完全相同,除了那些被摧毁的城堡之外。 + +> 注意!如果您编辑任何继承的属性,它将自动在其所属部分中关闭继承,而无需先切换“继承”按钮。 + +要创建房间继承,只需在“资源浏览器”中选择要创建子房间的房间,然后单击鼠标右键![RMB Icon](images/Room Inheritance/Icon_RMB.png)。这将打开房间菜单,您可以在其中选择**Create Child**来创建新的子房间。这将在资源浏览器中可视化地创建并“附加”到父级: + +![Room Inheritance](images/Room Inheritance/Editor_RoomInheritance.png) + +要更改房间顺序和/或继承,您需要使用“房间管理器”,您可以使用“资源浏览器”右上方的菜单将其打开。在这里,您可以转到``继承''选项卡,然后单击![LMB Icon](images/Room Inheritance/Icon_LMB.png)并将任何房间拖到任何其他房间,以使其成为该房间的子房间(或将子房间拖离父房间并中断继承)。 + +请注意,在一个房间内,继承是多层的,您可以检查单个项目,单个图层上的项目组,项目属性或整个房间是否进行继承,也可以从连续房间继承(因此,房间C继承自B室,而B室又继承自A室)。例如,如果您创建一个除了视图设置以外就什么都没有的基础房间,则可以将所有其他房间创建为它的子房间,它们都将继承父房间的视图属性。但是,您可以更进一步,以“ view”房间作为其父级来创建另一个基础房间,其中包含常规GUI实例和每个游戏室所需的控制器。现在,您可以从中创建子房间,它们将从父房间继承控制器和GUI,并从该父房间继承视图设置! + +如您所愿,继承功能非常强大,您可以使用它来维护更整洁的房间结构,加快工作流程并总体上优化花在设计和设计房间上的时间。 +``` + +`GameMaker/The Asset Editors/Rooms/Room Properties.md`: + +```md +# Room Properties 房间属性 + +![Room Editor Properties](images/Room Properties/Editor_Room_RoomSettings.png) + +在“房间属性”部分中,您可以在此处设置设置的继承开关,以及为房间命名以及它是否为持久性。通常,当您离开一个房间并稍后返回同一房间时,该房间将重置为其初始设置。通常,这对于大多数游戏来说都可以,但是可能不是您想要的,例如RPG或任何非线性游戏,您想要在各个房间之间来回走动,让它们成为您上次离开的方式。选中标记为Persistent的框即可完成此操作。房间状态将被记住,当您稍后返回时,它将与您离开时的状态完全相同,只有在重新启动游戏时才会将其重置为开始状态。 请注意,这是一个例外-如果您将某些对象标记为持久对象,则该对象的实例将不会留在房间中,而是会移至下一个房间。 + +之后,您可以选择清除显示缓冲区。选中此选项时,将用一种颜色填充显示缓冲区。如果您知道视图将始终覆盖整个屏幕,或者您已绘制了全屏背景,则可以取消选中此复选框(这样可以节省重新绘制的内容,从而有助于优化游戏),但是如果您具有重叠的视图,这些视图在屏幕上留下空白区域或背景具有任何透明度时,应进行检查,以确保没有视图的区域被您选择的绘制颜色填充。目前,只能使用window_set_colour()通过代码设置此颜色。 + + + +> 注意:如果您正在使用自动纵横比校正(在“游戏选项”中设置),则应始终选中此选项,否则在绘制游戏的“信箱”中可能会得到奇怪的效果。如果您不使用此选项,则可以取消选中此选项,并可以小幅提升游戏性能(在Android和其他移动平台上尤其明显)。 + + + +房间必须具有大小,该大小由您输入的宽度和高度(以像素为单位)的值来定义,一旦设置好,您就可以继续添加创建代码(如果需要)。创建代码是从底部的按钮添加的(带有“继承”开关以指示房间是否应该继承其创建代码),如果单击它,则将打开代码或DnD™编辑器。该编辑器允许您输入将在会议室开始时,所有实例的create事件之后,但在其会议室开始事件之前运行的功能/动作和代码(有关事件顺序的更多信息,请参见此处)。该代码将在您每次进入房间时运行,除非房间被标记为永久房间,在这种情况下,该代码仅在首次进入房间时才运行一次,但不会在随后的访问中运行一次。 + +房间属性中的最后一个按钮允许您打开“实例创建顺序”窗口: + +![Instance Creation Order Window](images/Room Properties/Editor_Room_CreationOrder.png) + +此窗口按创建顺序列出了房间中的所有实例(从上到下)。如果您需要先创建特定实例,则只需单击并将其拖动到所需位置即可。请注意,将按照列表顶部到底部的顺序创建实例。 + + + +# Cameras And Viewports 相机和视窗 + +您可以定义的房间的下一组属性是与房间``摄像机''和``视口''有关的属性。摄像机视图提供了一种机制,可以在屏幕上不同位置绘制房间的不同部分,或者仅绘制房间的一部分以覆盖整个屏幕。例如,在大多数平台游戏中,摄影机视图跟随主要角色,因为,如果您可以在屏幕上看到整个关卡,则您的角色将太小而看不见,并且不会给玩家带来惊喜。摄像机视图还可以用于多人游戏或合作社游戏,因为它们允许您创建分屏设置,在该设置中,您可以在屏幕的一部分看到一个玩家,而在另一部分看到另一个玩家。 这可以在GameMaker Studio 2中使用相机视图轻松实现。 + +![Camera And Viewport Properties](images/Room Properties/Editor_Room_RoomViews.png) + +在视图属性的顶部,可以打开或关闭视图继承,然后有一个标记为“启用视口”的框。必须先标记此标记,然后才能在游戏中激活任何摄影机视图。 + + + +> *注意:您可以为三个主视图端口设置打开或关闭继承,然后为每个单独的摄像机视图打开或关闭继承。* + + + +下一个复选框是“清除视口背景”复选框,该复选框使用窗口颜色清除应用程序表面,如果您知道要覆盖整个表面,则可以不选中该复选框。 + +摄像机视图由两组不同的值定义,摄像机视图本身和要在其上绘制该视图的屏幕上的端口。有时这可能会引起混乱,因此让我们在解释如何定义它们每个之前先对其进行一些解释: + +- **The Camera**: 房间内的一个点,用于设置房间在屏幕上的显示方式(这是房间编辑器中的一个抽象点,其位置根据查看和查看端口设置自动设置) +- **The View**: 根据相机的位置,投影和旋转,相机看到的内容 +- **The View Port**: 物理屏幕上将显示摄像机视图的区域 + +![Camera View Example](images/Room Properties/Camera_Example.png) + +因此,例如,这意味着您可以将640x480的摄像机视图放到您的房间中,然后将端口设置为320x240,这将在屏幕上按比例缩小视图的大小显示该视图,您也可以执行相同的操作并将视图设置为较小的值,将端口设置为较大,以使图像放大到适合端口大小的大小,并且在屏幕上显示的内容将大于实际大小。这样,您可以在更改摄像机视图的同时保持屏幕(端口)的大小,以在屏幕的同一区域显示更多或更少的房间。 + +摄像机视图始终定义为房间中的矩形区域,您可以在其中指定左上角的位置,该区域的宽度和高度。然后,您必须通过定义视口来指定此区域在屏幕窗口中的显示位置,再次在该位置指定左上角的位置和大小(请注意,左上角的值不是0,0)可以给出奇怪的结果)。您可以有多个端口,并且它们可以重叠,在这种情况下,它们会按指示的顺序一个接一个地绘制。 + +请注意,整个屏幕区域始终被定义为矩形区域,因此即使偏移了端口,您的端口也将形成一个矩形,任何空白均由显示缓冲区的窗口颜色填充,这意味着在这种情况下,应该始终检查“清除显示缓冲区”,否则您将在端口之间的空间中看到奇特的伪影。下图说明了这一点,其中有两个可见的偏移视口,但它们形成一个正方形窗口,并且在“下方”有一条线绘制到显示缓冲区: + +![Object Editor Post Draw](images/Room Properties/Editor_Object_PostDraw.png) + +相机还具有“对象跟随”选项。这是当您希望摄像机“跟随”(即:保持视图聚焦)某个对象时。为此,您必须单击![LMB Icon](images/Room Properties/Icon_LMB.png)菜单图标上的并从弹出的列表中选择一个对象(如果房间中有该对象的多个实例,则相机仅跟随其中的一个实例)。 + +相机的正常行为是仅在跟随实例的位置过于靠近“缓冲区”区域(该区域在视图边缘周围形成不可见边界)时移动。您可以使用“水平边框”和“垂直边框”值来定义此区域,因此-例如-将这些值设置为64将意味着视图将不会开始移动并跟随字符,直到从边缘到64像素为止。看法。 + + 最后,您可以指示角色到达缓冲区时摄像机移动的水平和垂直速度,默认值为-1。该默认值基本上是“即时”的,表示跟随实例在水平边框或垂直边框缓冲区之外时,视图将跳至其当前位置。 现在,这并不总是您想要的,因此您可以通过将值设置为-1以外的值来设置摄像机的垂直和水平滚动速度。请注意,值0会导致视图完全不移动,而其他任何正值都是该视图在任何帧中将移动多少像素,因此将水平速度设置为5将使视图以每像素5个像素跟随对象水平构图。 + + + +# Room Physics 房间物理学 + +在游戏中使用内置的物理功能之前,必须告诉GameMaker Studio 2该房间是一个物理房间。为此,您必须在``房间物理''部分的顶部勾选``启用物理''选项(也可以独立于房间的其余部分切换此部分的继承)。完成此操作后,您就可以继续设置“物理世界”属性,这些属性是您必须预先定义的一些基本属性,您的房间才能允许物理实例照常工作。为了更精确地控制世界,您可以使用代码(有关更多信息,请参见物理功能)。 + +![Room Physics Properties](images/Room Properties/Editor_Room_RoomPhysics.png) + +您必须做的下一件事是建立世界的重力。强度和方向的计算方式是您在(0,0)点附近设置的x / y位置的向量。因此,x为0且y为1会将重力方向设置为以每秒1米的力向下(有关更详细的解释,请参见-物理世界)。 + +最后,您必须为GameMaker Studio 2设置像素与仪表的比率,以用作其所有物理计算的基础。这是因为物理函数在实际测量中起作用,这就是我们必须设置该值的原因,并且您需要调整此设置,直到所使用的对象的平均像素大小大致转换为模拟的物理对象为止。合适的尺寸。 + +应该注意的是,在房间中启用物理功能意味着房间中的所有实例都必须使用物理功能和变量来移动。基本上,您具有“传统”运动,可以在其中设置实例的X / Y位置或设置其速度和方向,然后进行“物理”运动,这需要物理力和冲动来移动。这些系统是互斥的,您不能使用非物理功能移动物理实例,也不能使用物理功能移动非物理实例。 +``` + +`GameMaker/The Asset Editors/Sounds.md`: + +```md +# The Sound Editor 声音编辑器 + +![Sound Editor](images/Sounds/Editor_Sound.png) + +在“声音编辑器”中,您可以将声音加载到新的音频资源中并设置其属性。要加载声音,请按声音名称右侧的“加载声音”按钮。弹出一个文件选择器对话框,您可以在其中选择声音文件。这可以是WAV,MP3文件或OGG文件。通常,`*.wav`用于短的声音效果,尽管它们通常是较大的文件,但由于它们不需要任何类型的解码来播放,因此它们可以立即播放。`* .mp3`或`*.ogg`格式的文件应用于背景音乐或更大或更大型的声音效果。这些文件比wave文件小得多,但CPU开销很小,因为在播放之前必须对其进行解码。 + +加载文件后,您可以使用播放按钮预览声音,该按钮将连续播放声音。这非常方便,因为这意味着您可以使用窗口底部的滑块来更改音量,并聆听其对声音的影响。还有一个停止按钮,因此您可以随时停止预览。 + +声音窗口还具有其他选项,如下所述: + + + +## Name And File + +在这里您可以单击打开按钮以打开文件浏览器并选择要加载的音频文件。您也可以从左侧的“Name”字段中设置新资源的名称。名称只能是字母数字,并且只能使用下划线“ _”符号,并且不能以数字开头。 + +## Attributes 属性 + +本部分用于设置声音属性。这些是导出游戏时将设置的属性,并且将影响运行时声音的播放方式。对于声音效果(WAV格式),您需要对其进行解压缩,以便它们可以快速播放并且不需要解码,但是对于OGG和MP3,通常需要其他三个选项之一。 + +压缩的音频将强制将所有声音文件(无论它们是* .wav还是* .mp3或* .ogg)压缩为所有平台的Ogg Vorbis * .ogg格式文件。这些声音在光盘上较小,但由于需要在播放之前先解压缩并加载到内存中,因此会占用少量CPU资源,因此,如果要在已经占用大量CPU的游戏中使用压缩,则应牢记这一点。 + +您可以通过选择在负载上解压缩来稍微减轻此CPU开销,这会将所有声音放入设备内存中以加快播放速度,但以增加内存使用为代价。 + + 如果选择了压缩音频,则还可以选择从磁盘流式传输声音。流式声音将是未经压缩并实时播放的声音,它是从光盘流式传输而不是加载到内存中的。流媒体是音乐的理想选择,因为它减少了解压缩整个文件的一次性开销-这可能会导致游戏暂停-但不建议用于简单的声音效果(CPU受到的影响要小得多)。如果您选择流音频,则无法将声音分配给音频组,并且此选项将被禁用,因为流声音的打包方式与其他声音不同,并且您可以完全控制它们的加载时间,使用代码播放和卸载。 + +重要的是要注意,在定位HTML5平台时。当播放请求不是直接来自用户交互(即触摸事件等)时,流式音频将无法在iOS上播放。 但是,由于GameMaker Studio 2将点击事件排队,并且仅在下一帧处理它们,因此在没有浏览器阻止的情况下播放流音频是不可能的。为了解决这个问题,在iOS浏览器上运行的任何HTML5游戏都将在引擎中将所有流音频视为未流音频,但不预加载流音频。 + +这意味着如果您的游戏在iOS浏览器上运行,并且-例如-您有10条音乐曲目都设置为流mp3文件。所有10个信号都将像未流音频一样进行初始化,但不会在游戏加载之前立即对其进行解码,这与正常流音频不同,后者会在加载屏幕阶段立即进行预加载/解码。 + +尽管这绕过了iOS问题并阻止了初始加载时间的大幅增加,但这确实意味着它可能会导致游戏已加载的状态,但是某些流声音仍可能正在下载或解码。 为了解决这个问题,您可以使用GML函数audio_sound_is_playable(),该函数检查是否可以实际播放给定的声音。在HTML5上,如果声音尚未完全加载或解码,则返回false;如果可以播放,则返回true(在所有其他平台上,它将始终返回true)。 + +## Volume + + 您可以在此处设置给定声音的播放音量。该值只是默认值,例如如果滑块在中间-例如-在游戏中使用时该声音的音量将为0.5。但是,如果您使用项目中的音频功能对增益进行任何更改,它将被覆盖。除了音量控制,还有回放控制,你可以预览声音,将其设置为循环或倒带。您也可以单击图标![Open Mixer Icon](images/Sounds/Icon_OpenMixer.png)在混音器中打开声音. + +## Target Options + +在“目标选项”面板中,可以设置游戏的转换率。这些设置将影响向目标平台导出声音的方式,GameMaker Studio 2将自动为您转换声音文件-您只需控制转换后这些声音的最终质量即可。 通常,默认设置为16位质量(或位深度),44100kHz采样率和192kbps比特率就足够了,但是您可能希望声音效果(例如)质量较低,而音乐则质量较高,在这种情况下,可以更改这些设置。 + +您还可以选择转换是单声道,立体声还是3D。如果计划使用音频发射器功能来创建3D声音效果,则应在此处进行说明,否则请根据要使用声音的用途选择单声道或立体声。如果您选择将声音用于3D音频,则将其导出为单声道声音。 + +> *注意:声音的质量越高,文件越大,在决定选择哪种比特率和采样率时应考虑到这一点。* + +## Audio Group + +您可以将音频文件分配给特定的音频组。这些是从``音频组''窗口创建的,用于将音频资产分组在一起,以简化内存管理和音量控制。 + +将音频文件分配给一个组后,您便可以使用特定的音频组功能来更改它们,并从内存中加载/卸载它们。有关更多信息,请参见音频功能的参考部分。 + +如果您选择了流音频,则此选项将被禁用,因为流声音的打包方式与其他声音不同,不会被加载到内存中,而是直接从存储介质中流化。 + +请注意,尽管其余音频属性是特定于配置的,但是音频组设置不是,音频将在您使用的所有配置上属于同一组。 + + +``` + +`GameMaker/The Asset Editors/Sprites.md`: + +```md +# The Sprite Editor 精灵编辑器 + +![The Sprite Editor](images/Sprites/Editor_Sprites.png) + +在Sprite Editor中,您可以设置自己制作的Sprite的常规属性。您可以通过以下方式打开此窗口:创建一个新的精灵(在资源浏览器上单击鼠标右键![RMB Icon](images/Sprites/Icon_RMB.png),然后选择Create Sprite),双击![LMB Icon](images/Sprites/Icon_LMB.png)现有的精灵,或者右键![RMB Icon](images/Sprites/Icon_RMB.png)单击现有的精灵并选择**Properties**。 + +组成Sprite Editor的部分如下: + + + +## Sprite Name + +在这里您可以命名精灵。精灵名称是整个游戏项目中用来将精灵标识为资源的名称,只能为字母数字,并且只能使用下划线'_'符号。尽管您可以在精灵名称中使用数字,但是名称不能以数字开头,因为使用GML或DnD™时,它将使其成为无效的资源名称。 + +## Image Properties + +在本节中,您可以通过单击“**Sprite Size**”按钮![Sprite Size Icon](images/Sprites/Icon_SpriteSize.png)设置图像大小。这将打开以下窗口: + +![Sprite Editor Resize Properties](images/Sprites/Editor_Sprites_ResizeProperties.png) + +在这里,您可以看到在应用选定的转换之前和之后的精灵预览,以下部分用于设置新的精灵属性,并分为两部分: + +- **Scale Image**: 缩放图像:使用此选项,您可以调整精灵绘制画布的大小,并且精灵中包含的所有图像都会被拉伸和缩放以适应新的大小。您可以选择缩放并保持基本图像的长宽比,还可以选择以像素或百分比缩放。根据您使用的图像类型,您可能还希望启用或不启用线性插值(通常,线性插值将使区域上的像素“平滑”,因此最好不要使用像素图)。 +- **Resize Canvas**:调整画布大小:此选项将在不缩放内容的情况下将绘图画布调整为新大小。您可以使用方向箭头设置调整大小的“锚点”,然后设置大小值。调整画布大小时,有一个选项可以保持纵横比,在底部,您可以选择以像素或百分比设置调整大小值。 + +对新大小满意后,可以单击“*Apply* ”以应用更改并关闭“调整大小”属性窗口。请注意,在将大小或大小的更改应用于子画面时,您可以选择将更改也应用于子画面的原点,方法是选中*Apply* 按钮旁边的**Apply to Origin**复选框。选中后-如果原点不是默认设置之一且未锁定在sprite中-则将缩放其位置以匹配更改与sprite的大小。如果未选中,则不会将缩放比例应用于原点。如果原点不是自定义值或已被锁定,则原点将变灰且不可用。您可以从“精灵编辑器首选项”为所有精灵设置此选项。 + +> *注意:此选项仅适用于位图精灵,而对于SWF或骨架动画精灵将显示为灰色。* + +在此部分中,您也可以打开图像编辑器以绘制您的精灵,或者您可以单击``导入''按钮导入以前创建的精灵。可以从大多数常见的位图格式(PNG,JPG,GIF等)导入子画面,但GameMaker Studio 2还支持矢量子画面(SWF)和骨骼动画子画面(Spine)。有关这些格式的更多信息,请参见“非位图精灵”部分。请注意,在导入位图精灵时,您可以在文件资源管理器中选择多个图像文件,并且每个图像文件将作为单独的子图像(帧)添加到相同的精灵中。 + +## Texture Settings 纹理设置 + +Sprite属性窗口的这一部分介绍GameMaker Studio 2如何将组成您Sprite的图像存储在纹理页面上,以用于设备和浏览器。对于桌面平台,这可能不太重要,但是当您开始为移动设备或Web开发时,对图像资产(纹理)的正确管理就变得非常重要,因为管理不当的纹理可能会对游戏产生不利影响,从而导致性能问题。 + +默认情况下,通常不选中“水平平铺”和“垂直平铺”复选框,因为大多数情况下您不想平铺精灵。但是,在某些情况下,您可能希望它们平铺,这意味着您应该检查这些选项,尤其是在要缩放视图或房间的情况下,因为如果未生成纹理页面,则缩放会在游戏图形中引入伪影适当地。 + +如果您的精灵将在3D游戏中用作纹理贴图,则应选中“单独的纹理页面”框,然后将为精灵赋予一个单独的纹理页面(请注意,每个子图像也将获得其纹理页面)自己的纹理页面,因此,如果您的精灵有10张图像,您将生成10个纹理页面.这将增加游戏的纹理内存需求,因此在使用此选项时必须格外小心。还要注意,一般来说,如果您的精灵正在使用此选项进行3D渲染,则纹理应为2的幂(即:128x128、256x256、512x512等)。 + +下一个选项是为子画面图像启用**Pre-Multiplied Alpha** 。启用后,它将预乘精灵所有子图像的Alpha。通常,这仅是将sprite绘制到表面上或用于某些特定特殊效果的必要条件。对于普通精灵,您应该看不到普通精灵和预乘精灵之间的任何明显差异。 + +我们还有一个边缘过滤(**Edge Filtering** )选项。此选项特别适用于为游戏启用了线性插值并且在子画面周围看到颜色“晕”的情况。这是由于硬件同时从纹理页面上的多个源纹理插入而造成的,当您按分辨率放大游戏时,这一点尤其明显。启用此功能将对边缘像素进行滤波,以呈现最接近的完整Alpha Sprite像素的颜色,因此将插值的像素混合在一起,而不会出现以前存在的光晕。 + +最后,可以选择希望精灵资源所属的纹理组。 基本上,纹理组(以前在纹理组管理器中定义)是可以设置的,以便可以将特定房间或游戏级别所需的所有图像资源存储在一起。因此,例如,您可以将所有1级图像放在一个纹理组中,而所有2级图像放在另一个纹理组中,等等,而GameMaker Studio 2会尝试将所有这些分组的资源放在同一纹理页面上以减少纹理页面在所选目标平台上运行游戏时进行交换。 + +> *注意:这可能并非总是必要的,此方法的性能提高将取决于目标设备是CPU绑定还是GPU绑定。* + +您可以在[here](https://manual-en.yoyogames.com/Settings/Texture_Information/Texture_Pages.htm)找到有关纹理页面的更多信息。 + +纹理组仅与位图图像相关,非位图精灵不会分配给纹理组,并且上述某些选项将不可用。一些非位图精灵已经具有自己独特的纹理页面(骨架动画精灵),而其他一些则不需要纹理,因为它们由矢量组成(SWF精灵)。有关更多信息,请参见以下部分: + +- [Non-Bitmap Sprites](https://manual-en.yoyogames.com/Settings/Texture_Information/Non-Bitmap_Sprites.htm) + +## Collision Mask 碰撞遮罩 + +所有的精灵都有一个碰撞蒙版,它对应于将精灵分配给实例时将用于检测冲突的精灵区域。您可以首先设置防撞蒙版的模式,该模式可以是以下之一: + +- **Automatic**: 自动:GameMaker Studio 2将根据子画面的Alpha值自动计算碰撞蒙版(这取决于下面介绍的“容差”设置)。 +- **Full Image**: 完整图像:将根据蒙版的左边界,右边界,上边界和下边界来计算蒙版。 +- **Manual**: 手动:选择此选项表示您希望使用左,右,顶部和底部的输入值来设置碰撞蒙版的边界。 + +除了“碰撞蒙版”模式外,还可以从下拉菜单中选择各种类型的碰撞蒙版: + +- **Rectangle**: 矩形:一个简单的矩形碰撞防护罩。在项目中使用时,这是最快的解决方法。 +- **Rotated Rectangle**: 旋转矩形:这是一个简单的矩形碰撞蒙版,它将与分配了Sprite的实例的image_angle值一起旋转。它比常规的矩形防碰撞罩要慢解决,但比精确的防碰撞罩要快。 +- **Ellipse**: 椭圆:创建椭圆碰撞蒙版。这比矩形蒙版的解析速度慢。 +- **Diamond**: 钻石:创建一个“钻石”碰撞蒙版。与矩形和椭圆形蒙版相比,这解决起来较慢,并且会产生更多的CPU开销。 +- **Precise**:精确:这会根据精灵的轮廓创建精确的碰撞蒙版。如果子画面具有多个子图像,则这将是彼此重叠的所有子图像的边缘的合成。请注意,如果将“模式”设置为“手动”并更改默认值,则精确蒙版将被剪切到边界框。这非常慢,并且会产生大量CPU开销。 +- **Precise (per frame)**:精确(每帧):这会在每帧的基础上创建一个精确的碰撞蒙版(即:该蒙版将更改为与每个不同帧的轮廓一致)。请注意,如果将“模式”设置为“手动”并更改默认值,则精确蒙版将被剪切到边界框。这是迄今为止解决的最慢的碰撞防护类型,除非在非常非常特殊的情况下以及很少的情况下,否则应避免这种情况。 + +下图说明了不同的遮罩选项: + +![Collisions](images/Sprites/bbox_image.png) + +碰撞检查选项对于您的游戏而言非常重要,因为它们将直接影响对象的交互方式和游戏的运行方式,错误的设置甚至会对整体性能产生负面影响。这是为什么?好吧,每当两个实例相遇且两个实例都具有有效的蒙版时,就会通过检查边界框和蒙版的重叠来生成碰撞事件,这可能是精确的,也可能不是精确的,并且是否适合图像索引。下图说明了这一点: + +![Different collisions and how they are resolved](images/Sprites/basic_collisions_image.png) + +在“碰撞蒙版”设置下,您具有“ alpha公差”滑块,该滑块指示相对于精灵像素的透明度创建蒙版的公差-具有较高部分透明度的像素保留在蒙版之外,而具有较低透明度的部分则保留在蒙版之外。遮罩的公差将考虑这些像素。这会影响边界框(未设置为手动时)和用于精确碰撞检查的蒙版。 + +最后,您可以设置蒙版的边界框限制。边界框是精灵蒙版所适合的矩形区域的名称。仅将此边界框内的像素用于遮罩,并且会参与碰撞检查,并且默认情况下会自动计算出该像素,同时考虑到alpha公差。如果您选中了“精确度(每帧)”,那么对于每个子图像,这也将是单独的。自行设置时,可以在左侧,右侧顶部和底部的框中输入值,也可以在Sprite预览窗口中单击![LMB Icon](images/Sprites/Icon_LMB.png)并拖动以在Sprite上``绘制''它。请注意,在“绘制”边界框时,会自动生成遮罩以适合最终值,并且边界框可以“剪切”遮罩。 + +值得注意的是,如果转到“预览”窗口,则可以使用每个角上的“手柄”来调整碰撞蒙版。单击并拖动它们将调整碰撞蒙版,与为边界框输入值相同。 + +## Broadcast Messages + +此按钮会将广播消息添加到Sprite时间轴,打开一个对话框,您可以在其中添加要广播的消息。该消息将添加到播放头在帧视图中的任何位置,并且由一个简单的字符串组成,可以在触发该消息并对其进行操作后在``广播消息事件''中进行选择。 + +![Add broadcast messages to a sprite](images/Sprites/Sprite_Broadcast_Message.gif) + +添加后,该消息将显示在``框架视图''中,您可以在其上单击鼠标![RMB Icon](images/Sprites/Icon_RMB.png)右键以打开菜单,您可以在其中删除或编辑该消息。有关广播消息的更多信息,请参见[here](https://manual-en.yoyogames.com/The_Asset_Editors/Sequence_Properties/Broadcast_Messages.htm). + +## Frame Speed + +在此部分中,您可以从左侧的输入框中设置动画帧的播放速度。帧速度设置可以基于“每秒帧数”或“每个游戏帧数”,其中30个“每秒帧数”的设置将在一秒钟内显示30帧动画,而30帧设置“每游戏帧的帧数”会尝试在单个游戏步骤中显示所有30帧,因此“每游戏帧的帧数”值通常设置为1或更低(例如:每游戏帧的帧数为0.5时,每隔一帧就会显示一个新帧两个游戏步骤)。 + +重要的是要注意,“帧速度”设置不仅会影响预览窗口中动画的播放,而且还会控制游戏中精灵的动画设置。 您在此处设置的速度将显示精灵预览,因为它将在游戏中以1的图像速度乘数运行。但是可以使用image_speed变量在代码中进行设置,这意味着精灵可以根据编程的内容更快或更慢地运行。因此,例如,如果您将精灵设置为使用10个“每秒帧数”,然后将image_speed设置为0.5,则游戏将以一半速度绘制精灵,即每秒绘制5帧。相反,如果我们将相同子画面的image_speed设置为2,则在游戏中它将以两倍的速度绘制动画-每秒20帧。请注意,也可以在图像编辑器中设置帧速。 + +## Sprite Origin 精灵原点 + +Sprite编辑器的顶部栏包含Sprite原点属性。这是精灵上与其在房间中的位置相对应的点,即:当您在特定的x / y位置创建实例时,精灵的原点放置在该位置。 + +默认情况下,原点是精灵的左上角,但是使用中间中心通常更方便,或者您可能希望使用其他位置(例如对于平台游戏角色,您可能需要底部居中),因此您可以在原点下拉菜单上单击![LMB Icon](images/Sprites/Icon_LMB.png)并选择默认值之一。您也可以通过以下方式手动设置原点:在Sprite预览图像中单击,这会将原点标记移至您单击![LMB Icon](images/Sprites/Icon_LMB.png)的点,或者在相应的框中输入x和y的不同值。请注意,您甚至可以通过使用负数(大于或等于小精灵的宽度和高度,如右和向下)设置负数(用于小数位数的左侧和上方),或者在小精灵区域之外设置原点,这在处理带有需要绘制复合精灵。 + +## Zoom Controls 缩放控制 + +在“预览”窗口的右上角,具有缩放控件。这些按钮控制当前的预览精灵缩放级别。 您可以放大或缩小,单击![Zoom Reset Icon](images/Sprites/Icon_ZoomReset.png)将在编辑精灵时将预览重置为1:1。您还可以单击窗口适合按钮,以使整个精灵预览适合当前编辑器窗口(这将适当放大/缩小以使其适合)。请注意,您可以使用![Control Icon](images/Sprites/Icon_Ctrl.png) / ![CMD Icon](images/Sprites/Icon_Cmd.png)和鼠标滚轮![MMB Icon](images/Sprites/Icon_MMB.png)进行放大和缩小,然后按![Control Icon](images/Sprites/Icon_Ctrl.png) / ![CMD Icon](images/Sprites/Icon_Cmd.png) + ![Enter Icon](images/Sprites/Icon_Enter.png)将预览与精算精灵大小设置为1:1。 + +## Preview 预览 + +Sprite预览窗口显示了游戏中将显示的Sprite,可以使用鼠标滚轮![MMB Icon](images/Sprites/Icon_MMB.png)进行放大或缩小,也可以使用鼠标中键![MMB Icon](images/Sprites/Icon_MMB.png)进行平移和拖动,或者使用 ![Space Icon](images/Sprites/Icon_Space.png) + ![LMB Icon](images/Sprites/Icon_LMB.png)组合拖动。您也可以使用“框”控件(如下所述)来更改预览图像的显示方式。 + +此窗口还将显示一个标记十字形原点的十字''(有关更多详细信息,请参见上面的十字形原点''部分),并且如果正在编辑该选项,则还可以显示碰撞蒙版''(请参见上方的碰撞蒙版''部分) )。 + +## Playback Controls 播放控制 + +播放控件用于在预览窗口中播放帧的动画,其中使用播放按钮![Sprite Play Icon](images/Sprites/Icon_Sprite_Play.png)开始/停止动画和循环![Sprite Loop Icon](images/Sprites/Icon_Sprite_Loop.png)按钮来设置子画面是否循环播放![Sprite Loop On Icon](images/Sprites/Icon_Sprite_LoopOn.png)(到达最后一帧时返回到第一帧)或乒乓球![Sprite Pingpong Icon](images/Sprites/Icon_Sprite_PingPong.png)(到达最后一帧时向后浏览帧)。 + +请注意,loop / ping-pong选项不会影响精灵在游戏中的显示方式,因此在精灵中设置这些选项不会更改游戏在编译和运行时的显示方式。对于可以在``帧视图''中设置的循环点(请参见下文了解更多信息),这也是正确的-这些循环点在sprite编辑器中完全可见,旨在帮助您测试动画,但不会影响动画的效果。 Sprite动画会显示在您的游戏中。 + +帧控件的另一部分是当前帧显示,并在左侧输入一点。该框显示了精灵动画的总帧数以及正在显示的当前帧。您可以随时单击输入框并输入要查看的帧号来更改当前帧。 + +> 重要的!显示的帧数从1到最大帧号,但是在您的游戏代码中,帧数从0到最大帧号-1。 + +## Frames + + Sprite编辑器的这一部分显示了沿序列时间轴组成单个Sprite集合的每个不同帧。 您可以在任何框架上单击![LMB Icon](images/Sprites/Icon_LMB.png)并向左或向右拖动以更改它们的动画显示顺序,也可以在任何框架上双击![LMB Icon](images/Sprites/Icon_LMB.png)以启动图像编辑器并进行编辑。您可以使用标准键盘快捷键或鼠标右键菜单选项来剪切,复制和粘贴框架,还可以使用![Control Icon](images/Sprites/Icon_Ctrl.png) / ![CMD Icon](images/Sprites/Icon_Cmd.png) + ![LMB Icon](images/Sprites/Icon_LMB.png)选择多个框架进行操作。 + +鼠标右键菜单具有以下选项: + +![RMB Frame View Menu](images/Sprites/Sprite_RMB_Frame_menu.png) + +- **Add Frame** - 添加帧-在动画的最后添加一个新帧。 +- **Insert Frame** - 插入帧-在当前选定帧之后的动画中插入新帧。 +- **Cut** / **Copy** / **Paste** - 剪切/复制/粘贴-剪切,复制和粘贴一帧或多帧。 +- **Delete Selected Frames** - 删除选定的帧-删除当前选定的一个或多个帧。 + +除了上面给出的拉伸框架的选项之外,您还可以将鼠标放在框架的边缘,然后当光标变为双箭头时,只需单击![LMB Icon](images/Sprites/Icon_LMB.png)并拖动以调整框架的大小,则需要更多时间: + +![Stretch a frame using the mouse](images/Sprites/Sprite_Stretch_Frame.gif) + +拉伸的帧将需要更多的时间显示,就像您多次复制同一帧一样。 + +最后,您还可以使用帧视图两侧的“句柄”来放大帧时间轴,如下图所示: + +![Sprite frame view zoom controls](images/Sprites/Sprite_Loop_Markers.gif) +``` + +`Git/git remote.md`: + +```md +## 查看当前的远程库 + +加上 `-v` 选项(译注:此为 `--verbose` 的简写,取首字母),显示对应的克隆地址: +``` +$ git remote -v +origin git://github.com/schacon/ticgit.git (fetch) +origin git://github.com/schacon/ticgit.git (push) +``` + +如果有多个远程仓库,此命令将全部列出。比如在我的 Grit 项目中,可以看到: +``` +$ cd grit +$ git remote -v +bakkdoor git://github.com/bakkdoor/grit.git +cho45 git://github.com/cho45/grit.git +defunkt git://github.com/defunkt/grit.git +koke git://github.com/koke/grit.git +origin git@github.com:mojombo/grit.git +``` +``` + +`Git/git tag.md`: + +```md + +## 显示现有的标签 +``` +$ git tag +v0.1 +v1.3 +``` + +- 如果标签太多的话,可以只列出自己感兴趣的标签 +``` +$ git tag -l 'v1.4.2.*' +v1.4.2.1 +v1.4.2.2 +v1.4.2.3 +v1.4.2.4 +``` + +## 分享标签 +在默认的情况下,`git push`并不会把标签传送到远端服务器,只有通过显式命令才能分享标签到远端仓库。使用命令 +`git push origin [tagname]` + +``` +$ git push origin v1.5 +Counting objects: 50, done. +Compressing objects: 100% (38/38), done. +Writing objects: 100% (44/44), 4.56 KiB, done. +Total 44 (delta 18), reused 8 (delta 1) +To git@github.com:schacon/simplegit.git +* [new tag] v1.5 -> v1.5 +``` + +如果要一次推送所有本地新增的标签上去,可以使用`--tags`选项: +``` +$ git push origin --tags +Counting objects: 50, done. +Compressing objects: 100% (38/38), done. +Writing objects: 100% (44/44), 4.56 KiB, done. +Total 44 (delta 18), reused 8 (delta 1) +To git@github.com:schacon/simplegit.git + * [new tag] v0.1 -> v0.1 + * [new tag] v1.2 -> v1.2 + * [new tag] v1.4 -> v1.4 + * [new tag] v1.4-lw -> v1.4-lw + * [new tag] v1.5 -> v1.5 +``` +``` + +`Git/git 使用目录.md`: + +```md +## 远程仓库 +- git remote +``` + +`Git/git 分支.md`: + +```md +## 本地分支重命名 + +``` +git branch -m oldname newname +``` + +## 远程分支重命名 + +> 如果修改远程分支,只需要将本地分支重命名为新分支名称,然后删除远程分支,再把本地分支上传即可 + + +## 本地分支删除 + +``` +git branch -D branchName +``` + +## 远程分支删除 +``` +git push origin :branchName (origin后面有空格) +``` +``` + +`Git/git常用命令.md`: + +```md +# Git常用命令 + + + +## 保存当前代码,但不提交 + +情景:在一个版本上修改bug,结果修改了很多之后,发现整个软件不能运行了,现在想暂存下这些代码,恢复到修改之前的代码 + + + +```shell +git stash save "修改信息" +``` + + + +![image-20201210193321666](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201210193321666.png) + +这样之后,代码就回到上一个commit了。 + + +## 解决 git pull/push 每次都要输入用户名密码的问题 + +```shell +git config --global credential.helper store +``` + +## 自动添加修改的文件并提交 + +把暂存区的所有被修改或者已删除的且已经被git管理的文档提交提交到分支,须输入描述信息(可省略 git add 过程) + +```shell +git commit -a -m "comment" +``` + + + + + +## 撤销git add + +add某个文件后,又反悔不想添加的时候 + +```shell +git reset --mixed // 这样文件退出暂存区,但是修改保留(这个应该是最好用的方式) + +git reset HEAD // file文件取消暂存 +``` + + + +## git打补丁 + + + +### patch和diff的区别 + +Git提供了两种补丁方案,一是用git diff生成的UNIX标准补丁`.diff`文件,二是`git format-patch`生成的Git专用`.patch`文件。 + + + +`.diff`文件只是记录文件改变的内容,不带有commit记录信息,多个commit可以合并成一个diff文件。 + +`.patch`文件带有记录文件改变的内容,也带有commit记录信息,每个commit对应一个patch文件。 + + + +### 创建patch和diff + +#### 1、创建patch 文件的常用命令行 + +##### *某次提交(含)之前的几次提交: + +```shell +git format-patch 【commit sha1 id】-n +``` + +n指从sha1 id对应的commit开始算起n个提交。 eg: + +```shell +git format-patch 2a2fb4539925bfa4a141fe492d9828d030f7c8a8 -2 +``` + +##### *某个提交的patch: + +```shell +git format-patch 【commit sha1 id】 -1 +``` + +eg: + +```shell +git format-patch 2a2fb4539925bfa4a141fe492d9828d030f7c8a8 -1 +``` + +##### *某两次提交之间的所有patch: + +```shell +git format-patch 【commit sha1 id】..【commit sha1 id】 +``` + +eg: + +```shell +git format-patch 2a2fb4539925bfa4a141fe492d9828d030f7c8a8..89aebfcc73bdac8054be1a242598610d8ed5f3c8 +``` + +#### 2、创建diff文件的常用方法 + +##### 使用命令行 + +```shell +git diff 【commit sha1 id】 【commit sha1 id】 > 【diff文件名】 +``` + +eg: + +```shell +git diff 2a2fb4539925bfa4a141fe492d9828d030f7c8a8 89aebfcc73bdac8054be1a242598610d8ed5f3c8 > patch.diff +``` + +### 应用patch 和 diff + +#### 相关命令行 + +##### 检查patch/diff是否能正常打入: + +```shell +git apply --check 【path/to/xxx.patch】 +git apply --check 【path/to/xxx.diff】 +``` + +##### 打入patch/diff: + +```shell +git apply 【path/to/xxx.patch】 +git apply 【path/to/xxx.diff】 +``` + +或者 + +```shell +git am 【path/to/xxx.patch】 +``` + +### 冲突解决 + +在打补丁过程中有时候会出现冲突的情况,有冲突时会打入失败,如图: + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/164d0feb20bb887e) + + + +此时需要解决冲突: + +1、首先使用 以下命令行,自动合入 patch 中不冲突的代码改动,同时保留冲突的部分: + +``` +git apply --reject xxxx.patch +``` + +可以在终端中显示出冲突的大致代码: + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/164d0feb478f6a3a) + +同时会生成后缀为 .rej 的文件,保存没有合并进去的部分的内容,可以参考这个进行冲突解决。 + +2、解决完冲突后删除后缀为 .rej 的文件,并执行`git add.`添加改动到暂存区. + +3、接着执行`git am --resolved`或者`git am --continue` + + + +说明:在打入patch冲突时,可以执行`git am --skip`跳过此次冲突,也可以执行`git am --abort`回退打入patch的动作,还原到操作前的状态。 + + + +``` + +`Git/搭建git服务器.md`: + +```md +在CentOs上安装git + +## 查看git服务 +先查看git服务,centos上的版本比较低 +``` +yum info git +``` +显示: +``` +[root@proud-cubes-3 ~]# yum info git +Loaded plugins: fastestmirror +Loading mirror speeds from cached hostfile + * base: centos.mirror.lax.us.hostlink.com.au + * elrepo-kernel: repos.lax-noc.com + * epel: d2lzkl7pfhq30w.cloudfront.net + * extras: centos.mirror.lax.us.hostlink.com.au + * updates: repos-lax.psychz.net +Installed Packages +Name : git +Arch : x86_64 +Version : 1.8.3.1 +Release : 20.el7 +Size : 22 M +Repo : installed +From repo : updates +Summary : Fast Version Control System +URL : http://git-scm.com/ +License : GPLv2 +Description : Git is a fast, scalable, distributed revision control system with an + : unusually rich command set that provides both high-level operations + : and full access to internals. + : + : The git rpm installs the core tools with minimal dependencies. To + : install all git packages, including tools for integrating with other + : SCMs, install the git-all meta-package. + +``` + +删除掉旧的版本: +``` +yum remove git +``` +https://mirrors.edge.kernel.org/pub/software/scm/git/git-2.9.5.tar.xz + +## 安装git + +### 安装信赖 + +``` +yum install autoconf curl-devel expat-devel openssl-devel zlib-devel perl-devel +``` + +### 下载git最新版本并编译安装 +``` +[root@tCentos7 ~]# cd /usr/local/src +[root@tCentos7 ~]# wget https://www.kernel.org/pub/software/scm/git/git-2.11.0.tar.gz +[root@tCentos7 ~]# tar -zvxf git-2.11.0.tar.gz +[root@tCentos7 ~]# cd git-2.11.0 +[root@tCentos7 ~]# make configure +[root@tCentos7 ~]# ./configure --prefix=/usr/local/git ### 也可以指定libicon -with-iconv=/usr/local/libiconv +[root@tCentos7 ~]# make && make install +``` +需要等待一段时间 + +安装完成后,添加到环境变量 +``` +vim /etc/profile +// 添加一条 +export PATH="/usr/local/git/bin:$PATH" +source /etc/profile //使配置立即生效 +git --version //查看版本号 +``` +添加软链接 +``` +ln -s /usr/local/git/bin/* /usr/bin/ +``` + +## 创建一个git帐户 + +``` +# groupadd git +# useradd git -g git +# passwd git #参数是用户名 +# su - git //切换git用户 +``` +> 最好切换到git用户,不然后面新建的git仓库都要改权限 + +## git手动管理 +``` +# 进入git账户的主目录 +cd ~ +# 创建.ssh的配置 +mkdir .ssh +# 进入.ssh目录并创建authorize_keys文件,此文件存放客户端远程访问的ssh公钥 +cd .ssh +touch authorized_keys +# 设置权限,此步骤不能省略,而且权限也不要改,不然会报错 +chmod 700 /home/git/.ssh/ +chmod 600 /home/git/.ssh/authorized_keys +``` +此时,服务端的配置基本完成,接下来需要把客户端的公钥拷贝到`authorized_keys`文件中。 + +> authorized_keys文件中的每个ssh的pub都要单独的一行,将密钥上传到.ssh目录 +``` +cat xxx.pub >> authorized_keys +``` +## 服务器创建测试git仓库 +在/home/git/目录下创建test.git目录 +``` +# 切换到git账号 +$ su git +# 进入git账号的用户主目录。 + +$ cd /home/git +# 在用户主目录下创建 test.git仓库的文件夹 + +$ mkdir test.git && cd test.git +# 在test.git目录下初始化git仓库 + +$ git init --bare +# 输出如下内容,表示成功 +Initialized empty Git repository in /home/git/test.git/ +``` + +> 当修改了ssh默认的端口时 +> git remote add origin ssh://git@xxx:port/repo.git + +## 为什么在git服务器上查看不到代码 +``` +# 在test.git目录下初始化git仓库 + +$ git init --bare +``` +由于上面初始化git仓库的时候有个`--bare`参数,所以这个仓库只存储代码历史提交信息,纯粹是为了共享,不允许用户直接登录服务器上去修改。 + +## 对git用户做一些限制 +默认情况下,我们新建帐号的登录shell是`/bin/bash`,这个配置在`/etc/passwd`文件中: +``` +root:x:0:0:root:/root:/bin/bash +git:x:1000:1000::/home/git:/bin/bash +``` +修改成 +``` +git:x:1000:1000::/home/git:/usr/bin/git-shell +``` + +``` + +`IDA/IDA介绍.md`: + +```md +IDA Pro是逆向分析工程的一个工具 + +## 反汇编 + +在传统的软件开发模型中,程序员使用编译器、汇编器和链接器中的一个或几个创建可执行程序。为了回溯编程过程(或对程序进行逆向工程),我们使用各种工具来撤销汇编和编译过程。 毫不奇怪,这些工具就叫做反汇编器和反编译器。反汇编器撤销汇编过程,因此,我们可以得到汇编语言形式的输出结果。 + + +“恢复源代码”总是充满吸引力,但也极其困难。下面列举若干原因,说明为何反汇编困难重重: + +- **编译过程会造成损失** 机器语言中没有变量或函数名,变量类型信息只有通过数据的用途(而不是显式的类型声明)来确定。看到一个32位的数据被传送,你需要进行一番分析,才能确定这个32位数据表示的到底是一个整数、一个32位浮点值还是一个32位指针。 + +- **编译属于多对多对应关系** 这意味着源程序代码可以通过许多不同的方式转换成汇编语言,而机器语言也可以通过许多不同的方式转换成源程序代码 + +- **编译器代码优化** 例如: + + ```C + int a = 2*3; + int c = a * b; + ``` + + 会优化为: c = 6b;再进行编译。 + + 其实这个例子并不准确,只是为了说明编译器在编译代码时,会对代码做优化,而反汇编时,是不可能恢复到编译之前的最原始的代码状态。 + +### 为何反汇编 + +使用反汇编工具是为了在没有源代码的情况下促进对程序的了解。需要进行反汇编的常见情况包括以下几种: + +- 分析恶意软件 + +- 分析闭源软件的漏洞 + +- 分析闭源软件的互操作性 + +- 分析编译器生成的代码,以验证编译器的性能和准确性 + +- 在调试时显示程序指令 + + +### 如何反汇编 + +在反汇编时需要区分其中的**代码**和**数据**,并把代码转换成汇编语言显示给用户。 + +### 基本的反汇编算法 + +- 第一步。确定进行反汇编的代码区域。可反汇编文件必须符合可执行文件的某种通用格式,如 Windows 所使用的可移植可执行 (Portable Executable,PE)格式或许多Unix系统常用的可执行和链接格式(Executable and linking format,ELF)。这些格式通常含有一种机制,用来确定文件中包含代码和代码入口点的部分的位置。 + +- 第二步。知道指令的起始地址后,下一步就是读取该地址(或文件偏移量)所包含的值, 并执行一次表查找,将二进制操作码的值与它的汇编语言助记符对应起来。如读到`0x94`时,汇编指令对应`jz`,但`jz`并不能单独执行,需要再检索几个额外的字节,组成`jz xxxx`形式,完成反汇编一条指令。 + +- 第三步。对反汇编指令进行输出。 + +- 第四步。输出一条指令后,继续反汇编下一条指令,并重复上述过程,直到反汇编完文件中的所有指令。 + +#### 线性扫描反汇编 + +线性扫描反汇编算法采用一种非常简单的方法来确定需要反汇编的指令的位置:一条指令结束、另一条指令开始的地方。反汇编从一个代码段的第一个字节开始,以线性模式扫描整个代码段,逐条反汇编每条指令,直到完成整个代码段。 这种算法并不会通过识别分支等非线性指令来了解程序的控制流。 + + +#### 递归下降反汇编 + +递归下降采用另外一种不同的方法来定位指令。递归下降算法强调控制流的概念。控制流根据一条指令是否被另一条指令引用来决定是否对其进行反汇编。 + + +## IDA Pro入门 + +交互式反汇编器专业版(Interactive Disassembler Professional),人们常称为 IDA Pro,或 简称为 IDA。IDA 是 Hex-Rays 公司的旗舰产品。 + +![idapro](./images/1575878402(1).jpg) + + +### IDA的目录结构 + +``` +├─cfg // 包含各种配置文件,包括ida.cfg、idagui.cfg等 +├─dbgsrv +├─idc // 包含 IDA的内置脚本语言 IDC所需的核心文件。 +├─ids // 包含一些符号文件(IDA 语法中的 IDS 文件),这些文件用于描述可被加载到IDA的二进制文件引用的共享库的内容 +├─loaders // 包含在文件加载过程中用于识别和解析 PE或 ELF等已知文件格式的 IDA扩展 +├─platforms +├─plugins // 包含专门为IDA提供附加功能(多数情况下由用户定义)的IDA模块 +├─procs // 包含已安装的 IDA 版本所支持的处理器模块。 +├─python +├─sig // 包含IDA在各种模式匹配操作中利用的现有代码的签名。通过模式匹配,IDA能够将代码序列确定为已知的库代码,从而节省大量的分析时间。 +└─til // 包含一些类型库信息 +``` + +### IDA的用户界面 + +启动界面 + +![quickstart](./images/1575879832(1).jpg) + +- **New** 选择New将启动一个标准的 File Open对话框来选择将要分析的文件。 + +- **Go** Go按钮终止加载过程,使 IDA打开一个空白的工作区。 + +- **Previous** 使用Previous按钮可以打开其下“近用过的文件”列表中的一个文件。 + +选择分析类型 + +![loadfile](./images/1575880068(1).jpg) + +开始分析之后,会生成数据库文件,包含分析的结果 + +![datafile](./images/1575880278(1).jpg) + + +用户界面 + +![userwindow](./images/1575879700(1).jpg) + +1. 工具栏区域包含与 IDA的常用操作对应的工具 +2. 彩色的水平带是 IDA的概况导航栏,也叫做导航带。导航带是被加载文件地址空间的线性视图。 +3. IDA为当前打开的每一个数据显示窗口都提供了标签。 +4. 反汇编视图是主要数据显示视图,它有两种不同的形式:图形视图(默认)和列表视图 +5. 使用图形视图时,显示区很少能够一次显示某个函数的完整图形。这时,图形概况视图 (仅在使用图形视图时显示)可提供基本图形结构的缩小快照。 +6. 列出IDA分析出的函数 +7. 输出窗口显示的是 IDA输出的信息。 + + +## 数据显示窗口 + +### 图形显示窗口 + +![graphwindow](./images/1575882696(1).jpg) + +会发现,IDA使用不同的彩色箭头区分函数块之间各种类型的流。。根据条件,在条件跳转位置终止的块可能会生成两种流:Yes边的箭头(是的,执行分支)默认为绿色No边的箭头(不,不执行分支)默认为红色只有一个后继块的基本块会利用一个正常边(默认为蓝色)指向下一个即将执行的块。 + +控制方式: + +- **平移** 除了使用“图形概况”窗口迅速定位图形外,你还可以通过单击和拖动图形视图的背景来定位图形。 + +- **重新调整块的位置(不建议)** 通过单击指定块的标题栏并将其拖动到一个新位置,用户可以移动图 形中的每一个块的位置。 + +- **分组和折叠块** 可以对块分组,每个块单独分组,或者与其他块一起分组;并可将分组后的块折叠起来,以减少显示的混乱程度。折叠块特别有用,可以帮助你追踪已经 分析过的块。要折叠块,可以右击块的标题栏,然后在出现的菜单上选择“Group Nodes”。 + +- **创建其他反汇编窗口** 如果你想要同时查看两个不同函数的图形,可以通过命令再次打开另一个反汇编窗口。这样打开的第一个反汇编窗口叫做 IDA View-A。随后的反汇编窗口叫做IDA View-B、IDA View-C,依次类推。每个反汇编窗口都独立于其他窗口。 + + +### 文本列表 + +![textview](./images/1575884276(1).jpg) + +1. 显示窗口的左边部分叫做箭头窗口,用于描述函数中的非线性流程。实线箭头表示非条件跳转,虚线箭头则表示条件跳转。 + +2. 位置2的声明(也出现在图形视图中)是IDA对于函数栈帧布局的准确估算。IDA会对函数栈指针及函数使用的任何栈帧指针的行为进行仔细分析,从而计算出该函数的栈帧的结构。 + +3. 位置3的注释(以分号开头)属于交叉引用。它表示另一个程序指令将控制权转交给交叉引用注释所在位置的指令。 + + +### 函数窗口 + +![functionwindow](./images/1575884714(1).jpg) + +列出了函数在二进制文件中的虚拟地址、函数长度、局部变量所占内存大小、局部变量所占内存大小等信息。 + +### 其它窗口 + +输出窗口 + +![outputwindow](./images/1575885029(1).jpg) + +输出窗口是IDA的输出控制台,从中可以找到与IDA所执行的任务有关的信息 + +十六进制窗口 + +![hexwindow](./images/1575885080(1).jpg) + +导入表窗口 + +![importwindow](./images/1575885157(1).jpg) + +导出表窗口 + +![exportwindow](./images/1575885203(1).jpg) + +结构体窗口 + +![structwindow](./images/1575885287(1).jpg) + +结构体窗口用于显示IDA决定在一个二进制文件中使用的任何复杂的数据结构(如C结构体和联合)的布局。在分析阶段,IDA会查询它的函数类型签名扩展库,设法将函数的参数类型与程序使用的内存匹配起来。 + +如果拥有头文件,可通过解析获得更多信息: + +![parseheader](./images/1575969118(1).jpg) + +枚举窗口 + +![enumwindow](./images/1575885345(1).jpg) +枚举窗口有点类似于结构体窗口。如果IDA检测到标准枚举数据类型(C enum),它将在枚举窗口中列出该数据类型 + +其他窗口 + +![otherwindow](./images/1575885580(1).jpg) +其他窗口可使用如图方式显示。 + + +## 反汇编导航 + +### 双击导航 + +![xref](./images/1575886406(1).jpg) + +1是已命名的导航目标,双击可跳转到对应位置 + +2是交叉引用被当成导航目标,双击可跳转到引用位置 + +![xref](./images/双击跳转.gif) + + +### 跳转到地址 + +![jumpaddress](./images/1575887173(1).jpg) + + +快捷键`G`打开`Jump to Address`对话框。 + + +### 搜索 + +![search](./images/1576058845(1).jpg) + +可通过`Search`搜索代码、数据、立即数等 + + +### 导航历史 + +![prehistory](./images/1575888036(1).jpg) + +![nexthistory](./images/1575888105(1).jpg) + +![fastheaderhistory](./images/1575888179(1).jpg) + + + +### 栈帧 + +因为IDA Pro是一种低级分析工具,要利用它的许多功能和显示窗口,需要用户熟悉低级编程语言,其中许多概念与生成机器语言和管理由高级程序使用的内存有关。 + +栈帧是在程序的运行时栈中分配的内存块,专门用于特定的函数调用。 + +![stack](./images/1575888667(1).jpg) + +#### 调用约定 + +1. C调用约定 + +2. stdcall调用约定 + +3. fastcall调用约定 + +4. thiscall调用约定 + + +## 反汇编操作 + +### 名称与命名 + +自动生成的名称: + +- 与虚拟地址有关的名称 + +- 栈帧变量有关的名称 + + +这些名称不能够帮助我们了解一个位置或变量的用途,因此也无法帮助我们了解程序的行为。在分析一个程序时可以将默认名称更改为更有意义的名称。 自动扩散。 + +如果要修改一个名称,只需单击希望修改的名称(使其突出显示),并使用热键`N`打开更名对话框。或者,右击需要修改的名称,并在出现的上下文菜单中选择`Rename`选项。 + + +#### 参数和局部变量 + +![argandvar](./images/1575956701(1).jpg) + +前缀为`arg`的代表参数 + +前缀为`var`的代表局部变量 + + +### IDA注释 + +分为普通注释和可重复注释 + +使用热键`:`可添加普通注释 + +选中注释行,使用热键`;`可添加可重复注释 + +![comments](./images/1575961028(1).jpg) + +区别为:可重复注释可扩散 + + +### 代码显示选项 + +Options -> General + +![options](./images/1575961637(1).png) + + +### 操作函数 + +有的时候,ID无法定位一个函数或者IDA无法正确确定函数的结束部分,这时候需要手动干预,以更正反汇编代码中的错误。 + +1. **新建函数** + 将光标放在将要包含在新函数中的第一个字节或指令上,然后选择Edit -> Functions -> Create Function,即可创建一个新函数。在必要时,IDA会将数据转换成代码。接下来,它会向前扫描, 分析函数的结构,并搜索返回语句。如果IDA能够找到正确的函数结束部分,它将生成一个新的 函数名,分析栈帧,并以函数的形式重组代码。如果它无法找到函数的结束部分,或者发现任何 非法指令,则这个操作将以失败告终。 +2. **删除函数** + 删除函数 你可以使用Edit -> Functions -> Delete Function命令删除现有函数。如果你认为IDA的自动分析 出现错误,你可能希望删除一个函数。 +3. **调整栈帧** + IDA会尽其所能跟踪函数内每一条指令上的栈指针的变化。IDA跟踪这种变化的 准确程度,在很大程度上影响着函数的栈帧布局的准确程度。如果IDA无法确定一条指令是否更改了栈指针,你就需要手动调整栈指针。 + +### 数据与代码互相转换 + +在自动分析阶段,字节有时可能被错误地归类。数据字节可能被错误地归类为代码字节,并被反汇编成指令;而代码字节可能被错误地归类为数据字节,并被格式化成数据值。 + +![exchange](./images/1575962952(1).jpg) + +如上图,本来应该是函数的数据,IDA没有解析出来,可以使用Edit -> Code 或者使用热键`C`反汇编所有字节。 + +也可以使用 Edit -> Data或者热键`D`来将代码转换为数据。 + +![exchange](./images/代码转数据.gif) + +![exchange](./images/数据转代码.gif) + + + +## 交叉引用 + +对二进制文件进行逆向工程时,经常提出的一些常见问题包括:“这个函数是在什么地方调用过”和“哪些函数访问了这个数据”。 + +### 交叉引用 + +IDA中的交叉引用通常简称为 `xref` + +在 IDA 中有两类基本的交叉引用:`代码交叉引用`和`数据交叉引用` + + +选中函数或数据,使用热键`X`,可显示交叉引用窗口: + +数据交叉引用: + +![dataxref](./images/1575971681(1).jpg) + +代码交叉引用: + +![xrefwindow](./images/1575964929(1).jpg) + + + +![xrefsgraph](./images/1575964532(1).jpg) + +**此函数由哪些函数调用** + +![graphto](./images/1575964633(1).jpg) + +**此函数调用了哪些函数** + +![graptfrom](./images/1575964679(1).jpg) + +动态演示 + +![idagraph](./images/IDA绘图.gif) + +``` + +`LICENSE`: + +``` + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +``` + +`LeetCode/code/104. 二叉树的最大深度/test/test.sln`: + +```sln + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "test\test.vcxproj", "{1F8FEE42-4BFB-4B27-8787-EA2F736156FF}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1F8FEE42-4BFB-4B27-8787-EA2F736156FF}.Debug|Win32.ActiveCfg = Debug|Win32 + {1F8FEE42-4BFB-4B27-8787-EA2F736156FF}.Debug|Win32.Build.0 = Debug|Win32 + {1F8FEE42-4BFB-4B27-8787-EA2F736156FF}.Release|Win32.ActiveCfg = Release|Win32 + {1F8FEE42-4BFB-4B27-8787-EA2F736156FF}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal + +``` + +`LeetCode/code/104. 二叉树的最大深度/test/test/test.cpp`: + +```cpp +#include + + struct TreeNode { + int val; + struct TreeNode *left; + struct TreeNode *right; + }; + + + +int maxDepth(struct TreeNode* root){ + if(root == NULL) + { + return 0; + } + + int i, j; + i = maxDepth(root->left); + j = maxDepth(root->right); + + return 1 + (i > j ? i : j); +} + + +int main(int argc, char* argv[]) +{ + return 0; +} + +``` + +`LeetCode/code/104. 二叉树的最大深度/test/test/test.vcxproj`: + +```vcxproj + + + + + Debug + Win32 + + + Release + Win32 + + + + {1F8FEE42-4BFB-4B27-8787-EA2F736156FF} + Win32Proj + test + + + + Application + true + Unicode + + + Application + false + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + + + + + +``` + +`LeetCode/code/104. 二叉树的最大深度/test/test/test.vcxproj.filters`: + +```filters + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + +``` + +`LeetCode/code/125. 验证回文串/test/test.sln`: + +```sln + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "test\test.vcxproj", "{0FAA5D50-9BCB-4603-89F6-820067B427A2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0FAA5D50-9BCB-4603-89F6-820067B427A2}.Debug|Win32.ActiveCfg = Debug|Win32 + {0FAA5D50-9BCB-4603-89F6-820067B427A2}.Debug|Win32.Build.0 = Debug|Win32 + {0FAA5D50-9BCB-4603-89F6-820067B427A2}.Release|Win32.ActiveCfg = Release|Win32 + {0FAA5D50-9BCB-4603-89F6-820067B427A2}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal + +``` + +`LeetCode/code/125. 验证回文串/test/test/test.cpp`: + +```cpp +#include +#include + +bool isPalindrome(char * s){ + + if(s == NULL) + { + return true; + } + + int sLen = strlen(s); + + for(int i=0, j=sLen-1; i='0' && s[i]<='9' || s[i] >= 'A' && s[i] <= 'Z' || s[i] >= 'a' && s[i] <= 'z')) + { + // i动 j不动,故j++一次 + j++; + continue; + } + + if(!(s[j]>='0' && s[j]<='9' || s[j] >= 'A' && s[j] <= 'Z' || s[j] >= 'a' && s[j] <= 'z')) + { + // j动 i不动,故i--一次 + i--; + continue; + } + + // 全部转大写 + if(s[i] >= 'a' && s[i] <= 'z') + { + s[i] -= 32; + } + + if(s[j] >= 'a' && s[j] <= 'z') + { + s[j] -= 32; + } + + if(s[i] != s[j]) + { + return false; + } + + } + + return true; + +} + + +int main(int argc, char* argv[]) +{ + isPalindrome("0P"); + return 0; +} + +``` + +`LeetCode/code/125. 验证回文串/test/test/test.vcxproj`: + +```vcxproj + + + + + Debug + Win32 + + + Release + Win32 + + + + {0FAA5D50-9BCB-4603-89F6-820067B427A2} + Win32Proj + test + + + + Application + true + Unicode + + + Application + false + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + + + + + +``` + +`LeetCode/code/125. 验证回文串/test/test/test.vcxproj.filters`: + +```filters + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + +``` + +`LeetCode/code/14. 最长公共前缀/test/test.sln`: + +```sln + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.40629.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "test\test.vcxproj", "{5FD20DEB-D18A-4122-847C-7827BFDF6CA0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5FD20DEB-D18A-4122-847C-7827BFDF6CA0}.Debug|Win32.ActiveCfg = Debug|Win32 + {5FD20DEB-D18A-4122-847C-7827BFDF6CA0}.Debug|Win32.Build.0 = Debug|Win32 + {5FD20DEB-D18A-4122-847C-7827BFDF6CA0}.Release|Win32.ActiveCfg = Release|Win32 + {5FD20DEB-D18A-4122-847C-7827BFDF6CA0}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal + +``` + +`LeetCode/code/14. 最长公共前缀/test/test/test.cpp`: + +```cpp +#include +#include +#include + +char * longestCommonPrefix(char ** strs, int strsSize) +{ + /* + * 思路: + * 先比较第一和第二个,如果有公共的前缀,则用前缀和第三个比较 + * 如果没有公共前缀,就直接返回,后面的不需要比较 + */ + + char * temp = (char*)malloc(128); //用来存储公共前缀 + memset(temp, 0, 128); + + if (strsSize == 0) + { + return ""; + } + + strcpy(temp, strs[0]); + int count = strlen(temp); //用来计数,公共前缀字符数为0时,则没有 + + for (int i = 0; i < strsSize; i++) + { + char * currentStr = strs[i]; + + if (count == 0) + { + return ""; + } + int j; + for (j = 0; j < count; j++) + { + if (temp[j] != currentStr[j]) + { + break; + } + } + + count = j; + temp[j] = 0; + } + + return temp; + +} + + +int main(int argc, char* argv[]) +{ + char * strs[3] = { "flower", "", "flight" }; + + longestCommonPrefix(strs, 3); + + return 0; +} + +``` + +`LeetCode/code/14. 最长公共前缀/test/test/test.vcxproj`: + +```vcxproj + + + + + Debug + Win32 + + + Release + Win32 + + + + {5FD20DEB-D18A-4122-847C-7827BFDF6CA0} + Win32Proj + test + + + + Application + true + v120 + Unicode + + + Application + false + v120 + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + false + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + +``` + +`LeetCode/code/14. 最长公共前缀/test/test/test.vcxproj.filters`: + +```filters + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + 源文件 + + + +``` + +`LeetCode/code/141. 环形链表/test/test.sln`: + +```sln + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.40629.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "test\test.vcxproj", "{49118DAB-2C0C-4404-B925-DF4F6E016150}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {49118DAB-2C0C-4404-B925-DF4F6E016150}.Debug|Win32.ActiveCfg = Debug|Win32 + {49118DAB-2C0C-4404-B925-DF4F6E016150}.Debug|Win32.Build.0 = Debug|Win32 + {49118DAB-2C0C-4404-B925-DF4F6E016150}.Release|Win32.ActiveCfg = Release|Win32 + {49118DAB-2C0C-4404-B925-DF4F6E016150}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal + +``` + +`LeetCode/code/141. 环形链表/test/test/test.cpp`: + +```cpp +#include + + +struct ListNode { + int val; + struct ListNode *next; +}; + +bool hasCycle(struct ListNode *head) { + + if (head == NULL || head->next == NULL) + { + return false; + } + + /* + * 思路:快慢指针,慢指针每次走一步 + * 快指针每次走两步 + * 如果有环的话,快指针一定会追到慢指针 + */ + struct ListNode * slow = head; + struct ListNode * fast = head; + + while (fast != NULL) + { + slow = slow->next; + fast = fast->next ? fast->next->next : NULL; + + if (fast == slow) + { + return true; + } + } + + return false; + +} + +int main(int argc, char* argv[]) +{ + return 0; +} + +``` + +`LeetCode/code/141. 环形链表/test/test/test.vcxproj`: + +```vcxproj + + + + + Debug + Win32 + + + Release + Win32 + + + + {49118DAB-2C0C-4404-B925-DF4F6E016150} + Win32Proj + test + + + + Application + true + v120 + Unicode + + + Application + false + v120 + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + +``` + +`LeetCode/code/141. 环形链表/test/test/test.vcxproj.filters`: + +```filters + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + 源文件 + + + +``` + +`LeetCode/code/19. 删除链表的倒数第N个节点/test/test.sln`: + +```sln + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.40629.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "test\test.vcxproj", "{051957AF-4EE5-4A1B-9F89-DDE75576ED8F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {051957AF-4EE5-4A1B-9F89-DDE75576ED8F}.Debug|Win32.ActiveCfg = Debug|Win32 + {051957AF-4EE5-4A1B-9F89-DDE75576ED8F}.Debug|Win32.Build.0 = Debug|Win32 + {051957AF-4EE5-4A1B-9F89-DDE75576ED8F}.Release|Win32.ActiveCfg = Release|Win32 + {051957AF-4EE5-4A1B-9F89-DDE75576ED8F}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal + +``` + +`LeetCode/code/19. 删除链表的倒数第N个节点/test/test/test.cpp`: + +```cpp +#include +#include +#include + +//Definition for singly-linked list. + +struct ListNode { + int val; + struct ListNode *next; +}; + +// 双指针解法 +struct ListNode* removeNthFromEnd(struct ListNode* head, int n) +{ + // struct ListNode * A = new struct ListNode; + struct ListNode * A = (struct ListNode *)malloc(sizeof(struct ListNode)); + A->next = head; + struct ListNode * first, *second; + first = A; + second = A; + + for (int i = 1; i <= n + 1; i++) + { + first = first->next; + } + + while (first) + { + first = first->next; + second = second->next; + } + + second->next = second->next->next; + + return A->next; + +} + + +struct ListNode* removeNthFromEnd2(struct ListNode* head, int n) +{ + int count = 0; + + struct ListNode * temp = head; + + while (temp !=0) + { + count++; + temp = temp->next; + } + + if (count == n) + { + return head->next; + } + + int i = 1; + for (temp = head; temp->next != NULL;) + { + if (i == count - n) // 找到要删除的前一个 + { + temp->next = temp->next->next; + break; + } + + temp = temp->next; + i++; + + } + + return head; +} + +// 下面这个是头节点不算数据的 + +struct ListNode* removeNthFromEnd1(struct ListNode* head, int n){ + /* + * 思路:遍历链表中有多少个节点,然后找到倒数第n个 + */ + int count = 0; + struct ListNode * temp = head; + while (temp->next) + { + count++; + temp = temp->next; + } + int i = 0; + for (temp = head; temp->next != NULL;) + { + if (i == count-n) // 找到要删除的前一个 + { + temp->next = temp->next->next; + break; + } + + temp = temp->next; + i++; + + } + return head; +} + +int main(int argc, char* argv[]) +{ + struct ListNode head, A, B, C; + memset(&head, 0, sizeof(ListNode)); + memset(&A, 0, sizeof(ListNode)); + memset(&B, 0, sizeof(ListNode)); + memset(&C, 0, sizeof(ListNode)); + + A.val = 1; + B.val = 2; + C.val = 3; + + head.next = &A; + A.next = &B; + B.next = &C; + + + removeNthFromEnd(&head, 3); + + return 0; +} + +``` + +`LeetCode/code/19. 删除链表的倒数第N个节点/test/test/test.vcxproj`: + +```vcxproj + + + + + Debug + Win32 + + + Release + Win32 + + + + {051957AF-4EE5-4A1B-9F89-DDE75576ED8F} + Win32Proj + test + + + + Application + true + v120 + Unicode + + + Application + false + v120 + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + +``` + +`LeetCode/code/19. 删除链表的倒数第N个节点/test/test/test.vcxproj.filters`: + +```filters + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + 源文件 + + + +``` + +`LeetCode/code/206. 反转链表/test/test.sln`: + +```sln + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.40629.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "test\test.vcxproj", "{A5FF070A-A5E4-433C-965D-A8425216E0D1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A5FF070A-A5E4-433C-965D-A8425216E0D1}.Debug|Win32.ActiveCfg = Debug|Win32 + {A5FF070A-A5E4-433C-965D-A8425216E0D1}.Debug|Win32.Build.0 = Debug|Win32 + {A5FF070A-A5E4-433C-965D-A8425216E0D1}.Release|Win32.ActiveCfg = Release|Win32 + {A5FF070A-A5E4-433C-965D-A8425216E0D1}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal + +``` + +`LeetCode/code/206. 反转链表/test/test/test.cpp`: + +```cpp +#include + + +struct ListNode { + int val; + struct ListNode *next; +}; + + +struct ListNode* reverseList1(struct ListNode* head) +{ + if (head == NULL || head->next == NULL) + { + return head; + } + + struct ListNode * pre = NULL; + struct ListNode * cur = head; + struct ListNode * next = cur->next; + + while (next != NULL) + { + next = cur->next; + cur->next = pre; + pre = cur; + cur = next; + } + + return pre; +} + + +struct ListNode* reverseList(struct ListNode* head){ + + if (head == NULL || head->next == NULL) + { + return head; + } + + struct ListNode * preNode = NULL; + struct ListNode * q = head; + struct ListNode * nextHead = q->next; + + + while (nextHead != NULL) + { + nextHead = q->next; + q->next = preNode; + preNode = q; + q = nextHead; + } + + return preNode; + +} + + +int main(int argc, char* argv[]) +{ + return 0; +} + +``` + +`LeetCode/code/206. 反转链表/test/test/test.vcxproj`: + +```vcxproj + + + + + Debug + Win32 + + + Release + Win32 + + + + {A5FF070A-A5E4-433C-965D-A8425216E0D1} + Win32Proj + test + + + + Application + true + v120 + Unicode + + + Application + false + v120 + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + +``` + +`LeetCode/code/206. 反转链表/test/test/test.vcxproj.filters`: + +```filters + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + 源文件 + + + +``` + +`LeetCode/code/21. 合并两个有序链表/test/test.sln`: + +```sln + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "test\test.vcxproj", "{476C80FC-555A-4FD1-BAA7-26F8D647F606}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {476C80FC-555A-4FD1-BAA7-26F8D647F606}.Debug|Win32.ActiveCfg = Debug|Win32 + {476C80FC-555A-4FD1-BAA7-26F8D647F606}.Debug|Win32.Build.0 = Debug|Win32 + {476C80FC-555A-4FD1-BAA7-26F8D647F606}.Release|Win32.ActiveCfg = Release|Win32 + {476C80FC-555A-4FD1-BAA7-26F8D647F606}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal + +``` + +`LeetCode/code/21. 合并两个有序链表/test/test/test.cpp`: + +```cpp +#include +#include + +struct ListNode { + int val; + struct ListNode *next; + }; + + + +struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){ + + if(l1 == NULL) + { + return l2; + } + + if(l2 == NULL) + { + return l1; + } + + if(l1->val < l2->val) + { + l1->next = mergeTwoLists(l1->next, l2); + return l1; + } + else + { + l2->next = mergeTwoLists(l1, l2->next); + return l2; + } +} + +int main(int argc, char* argv[]) +{ + return 0; +} + +``` + +`LeetCode/code/21. 合并两个有序链表/test/test/test.vcxproj`: + +```vcxproj + + + + + Debug + Win32 + + + Release + Win32 + + + + {476C80FC-555A-4FD1-BAA7-26F8D647F606} + Win32Proj + test + + + + Application + true + Unicode + + + Application + false + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + + + + + +``` + +`LeetCode/code/21. 合并两个有序链表/test/test/test.vcxproj.filters`: + +```filters + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + +``` + +`LeetCode/code/234. 回文链表/test/test.sln`: + +```sln + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "test\test.vcxproj", "{89F9923E-B1E0-427A-BF16-96B8155003E5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {89F9923E-B1E0-427A-BF16-96B8155003E5}.Debug|Win32.ActiveCfg = Debug|Win32 + {89F9923E-B1E0-427A-BF16-96B8155003E5}.Debug|Win32.Build.0 = Debug|Win32 + {89F9923E-B1E0-427A-BF16-96B8155003E5}.Release|Win32.ActiveCfg = Release|Win32 + {89F9923E-B1E0-427A-BF16-96B8155003E5}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal + +``` + +`LeetCode/code/234. 回文链表/test/test/test.cpp`: + +```cpp +#include + + +struct ListNode { + int val; + struct ListNode *next; +}; + + + +bool isPalindrome(struct ListNode* head){ + + if(head == NULL || head->next == NULL) + { + return true; + } + + struct ListNode * slow = head; + struct ListNode * fast = head; + + // 先定位到中点 slow开始就是下半部分 + + while(fast != NULL) + { + slow = slow->next; + fast = fast->next ? fast->next->next : NULL; + } + + // 翻转slow链表 只有多于1个节点才开始翻转 + struct ListNode * pre = NULL; + struct ListNode * cur = slow; + struct ListNode * next = slow; + while(next != NULL) + { + next = cur->next; + cur->next = pre; + + pre = cur; + cur = next; + } + + while(pre != NULL) + { + if(pre->val != head->val) + { + return false; + } + + pre = pre->next; + head = head->next; + } + + return true; + +} + + +int main(int argc, char* argv[]) +{ + struct ListNode A = {1,NULL}; + struct ListNode B = {2,NULL}; + struct ListNode C = {2,NULL}; + struct ListNode D = {1,NULL}; + A.next = &B; + B.next = &C; + C.next = &D; + D.next = NULL; + + isPalindrome(&A); + return 0; +} + +``` + +`LeetCode/code/234. 回文链表/test/test/test.vcxproj`: + +```vcxproj + + + + + Debug + Win32 + + + Release + Win32 + + + + {89F9923E-B1E0-427A-BF16-96B8155003E5} + Win32Proj + test + + + + Application + true + Unicode + + + Application + false + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + + + + + +``` + +`LeetCode/code/234. 回文链表/test/test/test.vcxproj.filters`: + +```filters + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + +``` + +`LeetCode/code/237. 删除链表中的节点/test/test.sln`: + +```sln + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.40629.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "test\test.vcxproj", "{EE9323F8-9D38-4381-9C36-917B112CC724}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EE9323F8-9D38-4381-9C36-917B112CC724}.Debug|Win32.ActiveCfg = Debug|Win32 + {EE9323F8-9D38-4381-9C36-917B112CC724}.Debug|Win32.Build.0 = Debug|Win32 + {EE9323F8-9D38-4381-9C36-917B112CC724}.Release|Win32.ActiveCfg = Release|Win32 + {EE9323F8-9D38-4381-9C36-917B112CC724}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal + +``` + +`LeetCode/code/237. 删除链表中的节点/test/test/test.cpp`: + +```cpp +#include + +/** +* Definition for singly-linked list. +* struct ListNode { +* int val; +* struct ListNode *next; +* }; +*/ + +struct ListNode { + int val; + struct ListNode *next; + +}; + +void deleteNode(struct ListNode* node) { + struct ListNode * nextNode = node->next; + node->val = nextNode->val; + node->next = nextNode->next; +} + +int main(int argc, char* argv[]) +{ + return 0; +} + +``` + +`LeetCode/code/237. 删除链表中的节点/test/test/test.vcxproj`: + +```vcxproj + + + + + Debug + Win32 + + + Release + Win32 + + + + {EE9323F8-9D38-4381-9C36-917B112CC724} + Win32Proj + test + + + + Application + true + v120 + Unicode + + + Application + false + v120 + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + +``` + +`LeetCode/code/237. 删除链表中的节点/test/test/test.vcxproj.filters`: + +```filters + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + 源文件 + + + +``` + +`LeetCode/code/242. 有效的字母异位词/test/test.sln`: + +```sln + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "test\test.vcxproj", "{25E77744-AAEF-4F9F-BE81-209CC121EB89}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {25E77744-AAEF-4F9F-BE81-209CC121EB89}.Debug|Win32.ActiveCfg = Debug|Win32 + {25E77744-AAEF-4F9F-BE81-209CC121EB89}.Debug|Win32.Build.0 = Debug|Win32 + {25E77744-AAEF-4F9F-BE81-209CC121EB89}.Release|Win32.ActiveCfg = Release|Win32 + {25E77744-AAEF-4F9F-BE81-209CC121EB89}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal + +``` + +`LeetCode/code/242. 有效的字母异位词/test/test/test.cpp`: + +```cpp +#include +#include + +bool isAnagram(char * s, char * t) +{ + int c[26] = {0}; + + int sLen = strlen(s); + int tLen = strlen(t); + + if(sLen != tLen) + { + return false; + } + + for(int i=0; i + + + + Debug + Win32 + + + Release + Win32 + + + + {25E77744-AAEF-4F9F-BE81-209CC121EB89} + Win32Proj + test + + + + Application + true + Unicode + + + Application + false + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + + + + + +``` + +`LeetCode/code/242. 有效的字母异位词/test/test/test.vcxproj.filters`: + +```filters + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + +``` + +`LeetCode/code/28. 实现strStr()/test/test.sln`: + +```sln + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.40629.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "test\test.vcxproj", "{3E909E5E-2E90-4A51-89F2-83B36A781F4B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3E909E5E-2E90-4A51-89F2-83B36A781F4B}.Debug|Win32.ActiveCfg = Debug|Win32 + {3E909E5E-2E90-4A51-89F2-83B36A781F4B}.Debug|Win32.Build.0 = Debug|Win32 + {3E909E5E-2E90-4A51-89F2-83B36A781F4B}.Release|Win32.ActiveCfg = Release|Win32 + {3E909E5E-2E90-4A51-89F2-83B36A781F4B}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal + +``` + +`LeetCode/code/28. 实现strStr()/test/test/test.cpp`: + +```cpp +#include +#include + +int strStr(char * haystack, char * needle){ + + if (haystack == NULL || needle == NULL || strlen(needle)==0) + { + return 0; + } + + int hLen = strlen(haystack); + int nLen = strlen(needle); + + // 如果needle长度大于haystack,肯定找不到 + if (nLen > hLen) + { + return -1; + } + + int index = -1; + + for (int i = 0; i < hLen; i++) + { + if (needle[0] != haystack[i]) + { + continue; + } + + // 找到h中第一个和n第一个字符相等的位置 + + //如果h剩余的长度小于n的长度,则找不到 + if (hLen - i < nLen) + { + return -1; + } + + index = i; + + //判断剩余的字符串开始比较,出错则继续上面的步骤 + for (int j = 0; j < nLen; j++,index++) + { + if (haystack[index] != needle[j]) + { + index = -1; + break; + } + } + + if (index == -1) + { + continue; + } + else + { + //这里是已经找到,后面不需要再找,直接退出 + return index - nLen; + } + + } + + return -1; +} + +int main(int argc, char* argv[]) +{ + strStr("hello", "ll"); + return 0; +} + +``` + +`LeetCode/code/28. 实现strStr()/test/test/test.vcxproj`: + +```vcxproj + + + + + Debug + Win32 + + + Release + Win32 + + + + {3E909E5E-2E90-4A51-89F2-83B36A781F4B} + Win32Proj + test + + + + Application + true + v120 + Unicode + + + Application + false + v120 + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + +``` + +`LeetCode/code/28. 实现strStr()/test/test/test.vcxproj.filters`: + +```filters + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + 资源文件 + + + +``` + +`LeetCode/code/283.移动零/test/test.sln`: + +```sln + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "test\test.vcxproj", "{81823CCB-07D8-4A33-90BE-274DE4815073}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {81823CCB-07D8-4A33-90BE-274DE4815073}.Debug|Win32.ActiveCfg = Debug|Win32 + {81823CCB-07D8-4A33-90BE-274DE4815073}.Debug|Win32.Build.0 = Debug|Win32 + {81823CCB-07D8-4A33-90BE-274DE4815073}.Release|Win32.ActiveCfg = Release|Win32 + {81823CCB-07D8-4A33-90BE-274DE4815073}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal + +``` + +`LeetCode/code/283.移动零/test/test/test.cpp`: + +```cpp +#include + +/* +输入: [0,1,0,3,12] +输出: [1,3,12,0,0] + */ + +/* + *题解: + *首先是每轮把一个0放到最后,故循环numssize次 + *其次,在内部循环中找到0开始的位置,它和它右边的数据换位即可 + */ + +void moveZeroes(int* nums, int numsSize) +{ + int i = 0; + int j = 0; + + for(i = 0; i < numsSize; i++) + { + for(j = 0; j < numsSize-1; j++) + { + if(nums[j] != 0) + { + continue; + } + int temp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = temp; + } + } +} + + +int main() +{ + int nums[5] = { 0, 1, 0, 3, 12 }; + + moveZeroes(nums, 5); + + for(int i=0; i < 5; i++) + { + printf("%d ", nums[i]); + } + + std::cout << "Hello World!\n"; +} +``` + +`LeetCode/code/283.移动零/test/test/test.vcxproj`: + +```vcxproj + + + + + Debug + Win32 + + + Release + Win32 + + + + {81823CCB-07D8-4A33-90BE-274DE4815073} + Win32Proj + test + + + + Application + true + Unicode + + + Application + false + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + + + + + +``` + +`LeetCode/code/283.移动零/test/test/test.vcxproj.filters`: + +```filters + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + +``` + +`LeetCode/code/344. 反转字符串/test/test.sln`: + +```sln + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "test\test.vcxproj", "{E3912D22-DD8A-48A7-8F75-0E4061F987A1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E3912D22-DD8A-48A7-8F75-0E4061F987A1}.Debug|Win32.ActiveCfg = Debug|Win32 + {E3912D22-DD8A-48A7-8F75-0E4061F987A1}.Debug|Win32.Build.0 = Debug|Win32 + {E3912D22-DD8A-48A7-8F75-0E4061F987A1}.Release|Win32.ActiveCfg = Release|Win32 + {E3912D22-DD8A-48A7-8F75-0E4061F987A1}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal + +``` + +`LeetCode/code/344. 反转字符串/test/test/test.cpp`: + +```cpp +#include + + +/** + 输入:["h","e","l","l","o"] + 输出:["o","l","l","e","h"] + */ + +/** + *思路:第一个和最后一个换 + *第二个和倒数第二个换 + *以此类推 + */ + +void reverseString(char* s, int sSize) +{ + int last = sSize - 1; + for(int i=0; i + + + + Debug + Win32 + + + Release + Win32 + + + + {E3912D22-DD8A-48A7-8F75-0E4061F987A1} + Win32Proj + test + + + + Application + true + Unicode + + + Application + false + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + + + + + +``` + +`LeetCode/code/344. 反转字符串/test/test/test.vcxproj.filters`: + +```filters + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + +``` + +`LeetCode/code/36. 有效的数独/test/test.sln`: + +```sln + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "test\test.vcxproj", "{92CD7EFB-899F-433A-94D8-6F3C55E528CA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {92CD7EFB-899F-433A-94D8-6F3C55E528CA}.Debug|Win32.ActiveCfg = Debug|Win32 + {92CD7EFB-899F-433A-94D8-6F3C55E528CA}.Debug|Win32.Build.0 = Debug|Win32 + {92CD7EFB-899F-433A-94D8-6F3C55E528CA}.Release|Win32.ActiveCfg = Release|Win32 + {92CD7EFB-899F-433A-94D8-6F3C55E528CA}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal + +``` + +`LeetCode/code/36. 有效的数独/test/test/test.cpp`: + +```cpp +#include + +/* + [ + ["5","3",".",".","7",".",".",".","."], + ["6",".",".","1","9","5",".",".","."], + [".","9","8",".",".",".",".","6","."], + ["8",".",".",".","6",".",".",".","3"], + ["4",".",".","8",".","3",".",".","1"], + ["7",".",".",".","2",".",".",".","6"], + [".","6",".",".",".",".","2","8","."], + [".",".",".","4","1","9",".",".","5"], + [".",".",".",".","8",".",".","7","9"] +] + */ + +bool isValidSudoku(char** board, int boardSize, int* boardColSize) +{ + // 先遍历行 + for(int i=0; i + + + + Debug + Win32 + + + Release + Win32 + + + + {92CD7EFB-899F-433A-94D8-6F3C55E528CA} + Win32Proj + test + + + + Application + true + Unicode + + + Application + false + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + + + + + +``` + +`LeetCode/code/36. 有效的数独/test/test/test.vcxproj.filters`: + +```filters + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + +``` + +`LeetCode/code/38. 报数/test/test.sln`: + +```sln + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.40629.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "test\test.vcxproj", "{332A2574-55CF-40D5-8E09-5A55E3A74779}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {332A2574-55CF-40D5-8E09-5A55E3A74779}.Debug|Win32.ActiveCfg = Debug|Win32 + {332A2574-55CF-40D5-8E09-5A55E3A74779}.Debug|Win32.Build.0 = Debug|Win32 + {332A2574-55CF-40D5-8E09-5A55E3A74779}.Release|Win32.ActiveCfg = Release|Win32 + {332A2574-55CF-40D5-8E09-5A55E3A74779}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal + +``` + +`LeetCode/code/38. 报数/test/test/test.cpp`: + +```cpp +#include +#include +#include + +using namespace std; + +string countAndSay(int n) +{ + if (n==1) + { + return "1"; + } + + //根据上一个结果求本次结果 + + string preResult = countAndSay(n - 1); + + string result; + int count = 1; + + for (int i = 0; i < preResult.size(); i++) + { + // 这里判断有多少个重复的字符 + if (preResult[i] == preResult[i+1]) + { + count++; + continue; + } + else + { + // 当找到一个不相等的时,开始拼接字符串 + result += to_string(count) + preResult[i]; + count = 1; + } + + } + + return result; + +} + +int main(int argc, char* argv[]) +{ + return 0; +} + +``` + +`LeetCode/code/38. 报数/test/test/test.vcxproj`: + +```vcxproj + + + + + Debug + Win32 + + + Release + Win32 + + + + {332A2574-55CF-40D5-8E09-5A55E3A74779} + Win32Proj + test + + + + Application + true + v120 + Unicode + + + Application + false + v120 + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + +``` + +`LeetCode/code/38. 报数/test/test/test.vcxproj.filters`: + +```filters + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + 源文件 + + + +``` + +`LeetCode/code/387. 字符串中的第一个唯一字符/test/test.sln`: + +```sln + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "test\test.vcxproj", "{2F371F8B-1CCE-4F63-B648-BCDDCB834981}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2F371F8B-1CCE-4F63-B648-BCDDCB834981}.Debug|Win32.ActiveCfg = Debug|Win32 + {2F371F8B-1CCE-4F63-B648-BCDDCB834981}.Debug|Win32.Build.0 = Debug|Win32 + {2F371F8B-1CCE-4F63-B648-BCDDCB834981}.Release|Win32.ActiveCfg = Release|Win32 + {2F371F8B-1CCE-4F63-B648-BCDDCB834981}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal + +``` + +`LeetCode/code/387. 字符串中的第一个唯一字符/test/test/test.cpp`: + +```cpp +#include +#include + +int firstUniqChar(char * s) +{ + int c[26] = {0}; + + for(int i=0; i < strlen(s); i++) + { + c[s[i] - 'a']++; + } + + for(int i=0; i < strlen(s); i++) + { + if(c[s[i] - 'a'] == 1) + { + return i; + } + } + return -1; +} + +int main(int argc, char* argv[]) +{ + return 0; +} + +``` + +`LeetCode/code/387. 字符串中的第一个唯一字符/test/test/test.vcxproj`: + +```vcxproj + + + + + Debug + Win32 + + + Release + Win32 + + + + {2F371F8B-1CCE-4F63-B648-BCDDCB834981} + Win32Proj + test + + + + Application + true + Unicode + + + Application + false + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + + + + + +``` + +`LeetCode/code/387. 字符串中的第一个唯一字符/test/test/test.vcxproj.filters`: + +```filters + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + +``` + +`LeetCode/code/8. 字符串转换整数 (atoi)/test/test.sln`: + +```sln + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.40629.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "test\test.vcxproj", "{BE7EE6CB-AC0C-4E37-B063-3528B5AEE5D4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BE7EE6CB-AC0C-4E37-B063-3528B5AEE5D4}.Debug|Win32.ActiveCfg = Debug|Win32 + {BE7EE6CB-AC0C-4E37-B063-3528B5AEE5D4}.Debug|Win32.Build.0 = Debug|Win32 + {BE7EE6CB-AC0C-4E37-B063-3528B5AEE5D4}.Release|Win32.ActiveCfg = Release|Win32 + {BE7EE6CB-AC0C-4E37-B063-3528B5AEE5D4}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal + +``` + +`LeetCode/code/8. 字符串转换整数 (atoi)/test/test/test.cpp`: + +```cpp +#include +#include + +int myAtoi(char * str){ + if (str == NULL) + { + return 0; + } + + // 忽略空格 + int index = 0; + while (str[index] == ' ') + { + index++; + } + + // 判断第一个数是否为数字或负号 + if (!(str[index] >= '0' && str[index] <= '9' || str[index] == '-' || str[index] == '+')) + { + return 0; + } + + int strLen = strlen(str); + + // 处理负数 + if (str[index] == '-') + { + int i = index+1; + int temp = 0; + while (str[i] >= '0' && str[i] <= '9' && i < strLen) + { + //判断是否存在溢出 + if (temp > 2147483648 /10) + { + return (0-2147483648); + } + if (temp == 2147483648 / 10 && str[i] - 48 >= 8) + { + return (0-2147483648); + } + + //str[i] - 48 字符0转数字0 + temp = temp * 10 + (str[i] - 48); + + i++; + } + return (0 - temp); + } + + // 处理正数 + + int i = 0; + if (str[index] == '+') + { + i = index + 1; + } + else + { + i = index; + } + int temp = 0; + while (str[i] >= '0' && str[i] <= '9' && i < strLen) + { + //判断是否存在溢出 + if (temp > 2147483647 / 10) + { + return 2147483647; + } + if (temp == 2147483647 / 10 && str[i] - 48 > 7) + { + return 2147483647; + } + + //str[i] - 48 字符0转数字0 + temp = temp * 10 + (str[i] - 48); + + i++; + } + return temp; + +} + + +int main(int argc, char* argv[]) +{ + myAtoi("2147483646"); + return 0; +} + +``` + +`LeetCode/code/8. 字符串转换整数 (atoi)/test/test/test.vcxproj`: + +```vcxproj + + + + + Debug + Win32 + + + Release + Win32 + + + + {BE7EE6CB-AC0C-4E37-B063-3528B5AEE5D4} + Win32Proj + test + + + + Application + true + v120 + Unicode + + + Application + false + v120 + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + +``` + +`LeetCode/code/8. 字符串转换整数 (atoi)/test/test/test.vcxproj.filters`: + +```filters + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + 资源文件 + + + +``` + +`LeetCode/两数之和.md`: + +```md +给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 + +你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。 + +示例: + +给定 nums = [2, 7, 11, 15], target = 9 + +因为 nums[0] + nums[1] = 2 + 7 = 9 +所以返回 [0, 1] + + +代码: +``` +int* twoSum(int* nums, int numsSize, int target) { + int i = 0; + int j = 0; + bool flag = false; + + for(i=0; i=0) + { + int currentData = digits[i]; + if(i==digitsSize-1) + { + currentData = (digits[i] + 1 + temp); + } + else + { + currentData = (digits[i] + temp); + } + temp = (currentData)/10; + digits[i] = currentData % 10; + i--; + } + if(temp == 1) + { + *returnSize = digitsSize + 1; + for(i = 0; i0,且a≠1),那么数x叫做以a为底N的**对数**,记作$x={log^{N}_{a}}$,读作以a为底N的对数,其中a叫做对数的**底数**,N叫做**真数**。 + +一般地,函数$y={log^{x}_{a}}$(a>0,且a≠1)叫做对数函数,也就是说以幂(真数)为自变量,指数为因变量,底数为常量的函数,叫对数函数。 + +## 时间复杂度 + +算法的时间复杂度,用来度量算法的运行时间,记作: T(n) = O(f(n))。它表示随着 输入大小n 的增大,算法执行需要的时间的增长速度可以用 f(n) 来描述。 + +如果一个算法的执行次数是 T(n),那么只保留最高次项,同时忽略最高项的系数后得到函数 f(n),此时算法的时间复杂度就是 O(f(n))。为了方便描述,下文称此为 大O推导法。 + +时间复杂度分析的基本策略是:从内向外分析,从最深层开始分析。如果遇到函数调用,要深入函数进行分析。 + +## 例题 + +1. 基础题 + +```C++ +void aFunc(int n) { + for (int i = 0; i < n; i++) { + for (int j = i; j < n; j++) { + printf("Hello World\n"); + } + } +} +``` + +参考答案: +当 i = 0 时,内循环执行 n 次运算,当 i = 1 时,内循环执行 n - 1 次运算……当 i = n - 1 时,内循环执行 1 次运算。 +所以,执行次数 T(n) = n + (n - 1) + (n - 2)……+ 1 = n(n + 1) / 2 = n^2 / 2 + n / 2。 +根据上文说的 大O推导法 可以知道,此时时间复杂度为 O(n^2)。 + +2. 进阶题 + +```C++ +void aFunc(int n) { + for (int i = 2; i < n; i++) { + i *= 2; + printf("%i\n", i); + } +} +``` + +参考答案: +假设循环次数为 t,则循环条件满足 2^t < n。 +可以得出,执行次数t = log(2)(n),即 T(n) = log(2)(n),可见时间复杂度为 O(log(2)(n)),即 O(log n)。 + +3. 再次进阶 + +```C++ +long aFunc(int n) { + if (n <= 1) { + return 1; + } else { + return aFunc(n - 1) + aFunc(n - 2); + } +} +``` + +参考答案: +显然运行次数,T(0) = T(1) = 1,同时 T(n) = T(n - 1) + T(n - 2) + 1,这里的 1 是其中的加法算一次执行。 +显然 T(n) = T(n - 1) + T(n - 2) 是一个斐波那契数列,通过归纳证明法可以证明,当 n >= 1 时 T(n) < (5/3)^n,同时当 n > 4 时 T(n) >= (3/2)^n。 +所以该方法的时间复杂度可以表示为 O((5/3)^n),简化后为 O(2^n)。 + +## 常用的算法复杂度 + +- 快速排序 O(nlogn) 证明太难了,有时间再看 + +``` + +`LeetCode/树.md`: + +```md +## 树的介绍 + +### 树的定义 +树就像一个族谱,也像一串葡萄。 + +递归定义: +1. 有且只有一个根节点 +2. 有若干个互不相交的子树,这些子树本身也是一棵树 + +### 树的结点 + +叶子结点:如果结点没有任何子结点,那么此结点称为叶子结点。 + +### 结点的度和层次 + +对于一个结点,拥有的子树数(节点有多少分支)称为结点的**度**。子节点的个数称为度。 + +结点的层次:一棵树的深度(高度)是树中结点所在的最大层次。深度:从根节点到最底层节点的层数称之为深度,根节点是第一层。 + +### 叶子节点 + +没有子节点的节点 + +### 有序树和无序树 + +如果树中结点的子树从左到右看,谁在左边,谁在右边,是有规定的,这棵树称为`有序树`,反之称为`无序树`。 + +> 在有序树中,一个结点最左边的子树称为 **第一个孩子**,最右边的称为 **最后一个孩子**。 + +### 一般树 + +任意一个节点的子节点的个数都不受限制。 + +### 森林 + +由 m(m>=0)个互不相交的树组成的集合被称为森林。 + + +### 二叉树 + +满足两个条件的树就是二叉树: + +- 本身是有序树 +- 树中包含的各个节点的度不能超过2,即只能是0、1或者2. + +#### 分类 + +1. 一般二叉树 +2. 满二叉树 + 定义:在不增加层数的情况下,无法再添加一个节点的二叉树 +3. 完全二叉树 + 满二叉树是完全二叉树的一个特例 + +### 二叉树的性质 + +1. 二叉树中,第i层最多有$2^{i-1}$ +2. 深度为k的满二叉树必有$2^k-1$个节点,叶子数为$2^{k-1}$ +3. 满二叉树中不存在度为1的节点,每一个分支点中都有两棵深度相同的子树,且叶子节点都在最底层。 +4. 具有n个节点的满二叉树的深度为${log_{2}}^{n+1}$ + +## 完全二叉树 + +![image](./images/2-1Q22620003J18.gif) + +定义:如果二叉树中除去最后一层节点为满二叉树,且最后一层的结点依次从左到右分布,则此二叉树被称为完全二叉树。 + +完全二叉树除了具有普通二叉树的性质,它自身也具有一些独特的性质,比如说,n 个结点的完全二叉树的深度为 $\left \lfloor {log_{2}}^{n} \right \rfloor$+1 + +> $\left \lfloor {log_{2}}^{n} \right \rfloor$ 表示取小于 ${log_{2}}^{n}$ 的最大整数。例如,$\left \lfloor {log_{2}}^{4} \right \rfloor$ = 2,而 $\left \lfloor {log_{2}}^{5} \right \rfloor$ 结果也是 2。 向下取整。 + +对于任意一个完全二叉树来说,如果将含有的结点按照层次从左到右依次标号(如图 3a)),对于任意一个结点 i ,完全二叉树还有以下几个结论成立: + +1. 当 i>1 时,父亲结点为结点 [i/2] 。(i=1 时,表示的是根结点,无父亲结点) +2. 如果 2*i>n(总结点的个数) ,则结点 i 肯定没有左孩子(为叶子结点);否则其左孩子是结点 2*i 。 +3. 如果 2*i+1>n ,则结点 i 肯定没有右孩子;否则右孩子是结点 2*i+1 。 + +## 树的存储 + +### 遍历方式 + +如果用数组存储的话,不能只存储有效节点,不然不能用下面的遍历方式反推出原来的排列 + +1. 先序遍历 +2. 中序遍历 +3. 后序遍历 + +### 二叉树的顺序存储结构 + +优点: + 查找某个节点的父节点和子节点(也包括判断有没有子节点)很方便 +缺点: + 耗用内存很大 + +二叉树的顺序存储,指的是使用`顺序表(数组)`存储二叉树。需要注意的是,顺序存储只适用于完全二叉树。因此,如果我们想顺序存储普通的二叉树,需要提前将普通二叉树转化为完全二叉树。 + +![image](./images/2-1FS01056261L.png) +图1 + +普通二叉树转完全二叉树的方法很简单,只需给二叉树额外添加一些节点,将其拼凑成完全二叉树即可。 + +上图中,左侧是普通二叉树,右侧是转化后的完成(满)二叉树。 + +完全二叉树的顺序存储,仅需从根节点开始,按照层次依次将树中节点存储到数组即可。 + +![images](./images/2-1Q22H02935422.gif) +图2 + +如上图2在数组中的存储状态为下图: + +![images](./images/2-1FS0105529235.png) +图3 完全二叉树存储状态示意图 + +同样,图1中的存储结构为图4: + +![images](./images/2-1FS0105A9146.png) +图4 普通二叉树的存储状态 + +> 从顺序表中还原完全二叉树也很简单,根据完全二叉树的性质,将树中的节点按照层次并从左到右依次标号,若节点i有左右孩子,则其左孩子节点为`2*i`,右孩子为`2*i+1`.此性质可用于还原数组中存储的完全二叉树。**数组下标从1开始算的话,符合这个性质,下标从0开始的话,计算应按照左孩子为`2i+1`,右孩子为`2i+2`** + +### 二叉树的链式存储 + +如果二叉树不是完全二叉树,那么用数组存储的话,会浪费很多空间,所以用链表存储非完全二叉树。 + +![image](./images/2-1Q22R02H9133.gif) +图1 普通二叉树 + +如上图所示,若将其采用链表存储,则只需从树的根节点开始,将各个节点及其左右孩子使用链表存储即可。如下: + +![image](./images/2-1Q22R035341H.gif) +图2 二叉树链表存储结构示意图 + +表示一个节点结构的C语言代码为: + +```C++ +typedef struct _Node_{ + int data; + struct _Node_ * left; + struct _Node_ * right; +}Node, *PNode; +``` + +其实,二叉树的链表存储结构远不止图2所示的这一种。例如,在某些实际场景中,可能会做“查找某节点的父节点”的操作,这时可以在节点的结构中再添加一个指针,用于各个节点指向其父节点,如下所示: + +```C++ +typedef struct _Node_{ + int data; + struct _Node_ * left; + struct _Node_ * right; + struct _Node_ * parent; +}Node, *PNode; +``` +![image](./images/2-1Q22R0360I09.gif) + +这样的链表结构,通常称为三叉链表。 + +### 一般树的存储 + +1. 双亲表示法 + 求父节点方便 + 一个二维数组,一个空间存放数据本身,一个空间存放父节点的下标 +2. 孩子表示法 + 求子节点方便 + 一个二维数组,一个空间存放数据本身,它对应的其他空间存放子节点的链表 +3. 双亲孩子表示法 + 求父节点和子节点都很方便 + 1和2结合起来 +4. 二叉树表示法 + 把一个普通树转化成二叉树来存储 + 方法:设法保证任意一个节点的左指针域指向它的第一个孩子,右指针域指向它的兄弟。 + + 一个普通树转化成的二叉树一定没有右子树 + +### 森林的存储 + + 假如有 A、B、C三个树,则B是A的兄弟,C是B的兄弟 + 剩下的按照普通树转化成二叉树来操作 + +## 操作 + +### 遍历 + +下面以递归方式描述 + +#### 先序遍历 +1. 先访问根节点 +2. 再先序访问左子树 +3. 再先序访问右子树 + +#### 中序遍历 +1. 先中序遍历左子树 +2. 再访问根结点 +3. 再中序遍历右子树 + +#### 后序遍历 +1. 先后序遍历左子树 +2. 再后序遍历右子树 +3. 再访问根结点 + +### 已知两种遍历序列求原始二叉树 +通过先序和中序或者中序和后序,可以还原出原始二叉树 +必须包含中序 + +通过先序和后序是无法还原的 + +#### 已知先序和中序求原始二叉树 + +先序:ABCDEFGH +中序:BDCEAFHG + +在先序序列中先找根节点A,在中序序列中A左边的是左子树,右边的是右子树 +一直递归即可 + +中序:BDCEAFHG +后序:DECBHGFA + +在后序中找到A根节点,在中序中A左边是左子树,右边是右子树 +后序中最后出现的是根节点 + +一直递归 + +### 应用 + +树是数据库中数据组织一种重要形式 + +操作系统的子父进程的关系是一棵树 + +面向对象语言中类的继承关系 + +哈夫曼树 + +``` + +`LeetCode/算法概述.md`: + +```md +## 算法 + +解题的方法和步骤 + +## 衡量算法的标准 + +- 时间复杂度 + 大概程序要执行的次数,而非执行时间 +- 空间复杂度 + 算法执行过程中大概所占用的最大内存 +- 难易程度 +- 健壮性 + + +``` + +`Linux/Linux安装截图工具.md`: + +```md +# Linux 安装截图工具 + +## 使用ubuntu自带工具 + +```shell +superman@ubuntu:~$ gnome-screenshot +``` + +## 使用github开源工具 flamshot + +```shell +sudo apt install flamshot +``` + +使用时: + +```shell +flamshot gui +``` + +``` + +`Linux/Linux工具/Radare2.md`: + +```md +# Radare2 + +官方文档: [Introduction - The Official Radare2 Book](https://book.rada.re/index.html) + + + +说起逆向,你想到的可能是IDA Pro,OllyDBG。 + +而Radare*2*是一款开放源代码的逆向工程平台,它的强大超越你的想象,包括反汇编、分析数据、打补丁、比较数据、搜索、替换、虚拟化等等,同时具备超强的脚本加载能力,并且可以运行在几乎所有主流的平台(GNU/Linux, .Windows *BSD, iOS, OSX, Solaris…)上。可谓是一大神器。 + + + +## rabin2 + +radare2里面有个很牛逼的工具:rabin2 + + rabin2 可以获取包括ELF, PE, Mach-O, Java CLASS文件的区段、头信息、导入导出表、字符串相关、入口点等等,并且支持几种格式的输出文件。使用man rabin2 可以查看rabin2的使用帮助文档。 + +```shell +man rabin2 +``` + + + +![image-20201230155609153](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201230155609153.png) + + + +![image-20201230155713077](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201230155713077.png) + + + +显示section + +```shell +rabin2 -S bin +``` + + + +![image-20201230160101733](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201230160101733.png) + + + +## 常用命令 + + + +### seeking 定位 + +1. 定位 + + ```shell + [0x00400410]> s 0x0 + [0x00000000]> + ``` + + + +2. 查看当前位置 + + ```shell + [0x00000000]> s + 0x0 + [0x00000000]> + ``` + +3. 向前定位N + + ```shell + [0x00000000]> s+ 128 + [0x00000080]> + ``` + +4. 撤销前两次定位 + + ```shell + [0x00000000]> s- + [0x00000000]> s- + [0x00400410]> + ``` + + + +### block size 块大小 + +block size用来确定radare2在没有显示的指出要操作多少字节时,使用多少字节。 + +使用`b`命令来修改block size + +```shell +[0x00000000]> b 0x100 # block size = 0x100 +[0x00000000]> b+16 # ... = 0x110 +[0x00000000]> b-32 # ... = 0xf0 +``` + + + +### Mapping Files 文件映射 + +使用命令`o`打开一个文件。如果是一个可识别的二进制文件,则映射到对应的虚拟地址,其它的会映射到地址0. + + + +有时我们需要重建一个二进制或想把文件重新映射到别的地址。在启动r2时,使用`-B`参数进行基地址修改。但是需要注意的是,当打开一个不认识的文件时,需要使用`-m`参数(或当做`o`命令的参数) + + +``` + +`Linux/Linux开发/AT&T内联汇编.md`: + +```md +# 内联汇编 + +``` +__asm__("asm statements" : outputs : inputs : registers-modified); +``` + +## asm statements + +通常称为指令部,其格式和在汇编语言中使用的格式基本相同。 + + +在将芘主语句嵌入到C代码中时,操作数如何与C代码中的变量结合是个很大的问题:程序员提供具体的指令,而对寄存器的使用则只需给出样板和约束条件就可以了,具体如何将寄存器与变量结合起来完全由GCC和GAS来负责。 + +在GCC内联汇编语句的指令部中,加上前缀`%`的数字(如%0,%1)表示的就是需要使用寄存器的样板操作数。指令部中使用了几个样板操作数,就表明有几个变量需要与寄存器相结合。由于样板操作数也使用`%`作为前缀,因此在涉及到具体的寄存器时,寄存器名前面应该加上两个`%`,以免产生混淆。 + +## outputs + +紧跟在指令部后的是输出部,是规定输出变量如何与样板操作数进行结合的条件,每个条件称为一个`约束`,必要时可以包含多个约束,相互之间用逗号分隔开就可以了。每个输出约束都以`=`号开始,然后紧跟一个对操作数类型进行说明的字后,最后是如何与变量相结合的约束。凡是与输出部中说明的操作数相结合的寄存器或操作数本身,在执行完嵌入的汇编代码后均不保留执行之前的内容,这是GCC在调试寄存器时所使用的依据。 + + + + +## inputs + +输出部后面是输入部,输入约束的格式和输出约束相似,但不带`=`号。如果一个输入约束要求使用寄存器,则GCC在预处理时就会为之分配一个寄存器,并插入必要的指令将操作数装入该寄存器。与输入部中说明的操作数结合的寄存器或操作数本身,在执行完嵌入的汇编代码后也不保留执行之前的内容。 + + +## registers-modified + +有时在进行某些操作时,除了要用到进行数据输入和输出的寄存器外,还要使用多个寄存器来保存中间计算结果,这样就难免会破坏原有寄存器的内容。在GCC内联汇编格式中的最后一个部分中,可以对将产生副作用的寄存器进行说明,以便GCC能够采用相应的措施。 + + +## 例子 + +``` +int main() +{ + int a = 10, b = 0; + __asm__ __volatile__("movl %1, %%eax;/n/t" // 这个%1是输入操作数 + "movl %%eax, %0;" // 这个%0是输出操作数 + :"=r"(b) // 这里是output处,其实是相当于把%0换成b,改成 movl %eax, b + :"r"(a) // 这里是input处,其实是把%1换成a,改成 movl a, %eax + :"%eax"); // 由于上面的指令用到了eax寄存器,可能会影响到原有的值,在这里标示出来,让gcc编译器知道,好做后续准备。内联汇编语句的最后一个部分告诉GCC它将改变寄存器eax中的值,GCC在处理时不应使用该寄存器来存储任何其它的值。 + printf("Result: %d, %d\n", a, b); +} +``` + +https://www.cnblogs.com/hdk1993/p/4820353.html + +|限定符|意义| +|--|--| +|"m"、"v"、"o"|内存单元| +|"r"|任何寄存器| +|"q"|寄存器eax、ebx、ecx、edx之一| +|"i"、"h"|直接操作数| +|"E"和"F"|浮点数| +|"g"|任意| +|"a"、"b"、"c"、"d"|分别表示寄存器eax、ebx、ecx和edx| +|"S"和"D"|寄存器esi、edi| +|"I"|常数(0至31)| + +``` + +`Linux/Linux开发/ELF文件格式.md`: + +```md +# ELF文件格式 + + + +ELF (Executable and Linkable Format)是一种为**可执行文件,目标文件,共享链接库和内核转储**(core dumps)准备的标准文件格式。Linux和很多类Unix操作系统都使用这个格式。 + + + +一个ELF文件由以下三部分组成: + +- ELF头(ELF header) - 描述文件的主要特性:类型,CPU架构,入口地址,现有部分的大小和偏移等等; +- 程序头表(Program header table) - 列举了所有有效的段(segments)和他们的属性。 程序头表需要加载器将文件中的节加载到虚拟内存段中; +- 节头表(Section header table) - 包含对节(sections)的描述 + + + +## ELF头(ELF header) + +ELF头(ELF header)位于文件的开始位置。 它的主要目的是**定位文件的其他部分**。 文件头主要包含以下字段: + +- ELF文件鉴定 - 一个字节数组用来确认文件是否是一个ELF文件,并且提供普通文件特征的信息; +- 文件类型 - 确定文件类型。 这个字段描述文件是一个重定位文件,或可执行文件,或...; +- 目标结构; +- ELF文件格式的版本; +- 程序入口地址; +- 程序头表的文件偏移; +- 节头表的文件偏移; +- ELF头(ELF header)的大小; +- 程序头表的表项大小; +- 其他字段... + + + +```c +typedef struct elf64_hdr { + unsigned char e_ident[EI_NIDENT]; /* ELF "magic number" */ + Elf64_Half e_type; + Elf64_Half e_machine; + Elf64_Word e_version; + Elf64_Addr e_entry; /* Entry point virtual address */ + Elf64_Off e_phoff; /* Program header table file offset */ + Elf64_Off e_shoff; /* Section header table file offset */ + Elf64_Word e_flags; + Elf64_Half e_ehsize; + Elf64_Half e_phentsize; + Elf64_Half e_phnum; + Elf64_Half e_shentsize; + Elf64_Half e_shnum; + Elf64_Half e_shstrndx; +} Elf64_Ehdr; +``` + + + +## 程序头表(Program header table) + +```c +typedef struct elf64_phdr { + Elf64_Word p_type; + Elf64_Word p_flags; + Elf64_Off p_offset; /* Segment file offset */ + Elf64_Addr p_vaddr; /* Segment virtual address */ + Elf64_Addr p_paddr; /* Segment physical address */ + Elf64_Xword p_filesz; /* Segment size in file */ + Elf64_Xword p_memsz; /* Segment size in memory */ + Elf64_Xword p_align; /* Segment alignment, file & memory */ +} Elf64_Phdr; +``` + + + + + + + +## 节(sections) + + + +所有的数据都存储在ELF文件的节(sections)中。 我们通过节头表中的索引(index)来确认节(sections)。 节头表表项包含以下字段: + +- 节的名字; +- 节的类型; +- 节的属性; +- 内存地址; +- 文件中的偏移; +- 节的大小; +- 到其他节的链接; +- 各种各样的信息; +- 地址对齐; +- 这个表项的大小,如果有的话; + + + +```c +typedef struct elf64_shdr { + Elf64_Word sh_name; /* Section name, index in string tbl */ + Elf64_Word sh_type; /* Type of section */ + Elf64_Xword sh_flags; /* Miscellaneous section attributes */ + Elf64_Addr sh_addr; /* Section virtual addr at execution */ + Elf64_Off sh_offset; /* Section file offset */ + Elf64_Xword sh_size; /* Size of section in bytes */ + Elf64_Word sh_link; /* Index of another section */ + Elf64_Word sh_info; /* Additional section information */ + Elf64_Xword sh_addralign; /* Section alignment */ + Elf64_Xword sh_entsize; /* Entry size if section holds table */ +} Elf64_Shdr; +``` + + + +## vmlinux + +`vmlinux` 也是一个可重定位的ELF文件。 我们可以使用 `readelf` 工具来查看它。 首先,让我们看一下它的头部: + +readelf help + +```shell +[root@CentOS6 2.6.32-754.el6.x86_64]# readelf +Usage: readelf elf-file(s) + Display information about the contents of ELF format files + Options are: + -a --all Equivalent to: -h -l -S -s -r -d -V -A -I + -h --file-header Display the ELF file header + -l --program-headers Display the program headers + --segments An alias for --program-headers + -S --section-headers Display the sections' header + --sections An alias for --section-headers + -g --section-groups Display the section groups + -t --section-details Display the section details + -e --headers Equivalent to: -h -l -S + -s --syms Display the symbol table + --symbols An alias for --syms + -n --notes Display the core notes (if present) + -r --relocs Display the relocations (if present) + -u --unwind Display the unwind info (if present) + -d --dynamic Display the dynamic section (if present) + -V --version-info Display the version sections (if present) + -A --arch-specific Display architecture specific information (if any). + -c --archive-index Display the symbol/file index in an archive + -D --use-dynamic Use the dynamic section info when displaying symbols + -x --hex-dump= + Dump the contents of section as bytes + -p --string-dump= + Dump the contents of section as strings + -R --relocated-dump= + Dump the contents of section as relocated bytes + -w[lLiaprmfFsoR] or + --debug-dump[=rawline,=decodedline,=info,=abbrev,=pubnames,=aranges,=macro,=frames,=str,=loc,=Ranges] + Display the contents of DWARF2 debug sections + -I --histogram Display histogram of bucket list lengths + -W --wide Allow output width to exceed 80 characters + @ Read options from + -H --help Display this information + -v --version Display the version number of readelf +``` + + + +```shell +[root@CentOS6 2.6.32-754.el6.x86_64]# readelf -h vmlinux +ELF Header: + Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 + Class: ELF64 + Data: 2's complement, little endian + Version: 1 (current) + OS/ABI: UNIX - System V + ABI Version: 0 + Type: EXEC (Executable file) + Machine: Advanced Micro Devices X86-64 + Version: 0x1 + Entry point address: 0x1000000 + Start of program headers: 64 (bytes into file) + Start of section headers: 134752544 (bytes into file) + Flags: 0x0 + Size of this header: 64 (bytes) + Size of program headers: 56 (bytes) + Number of program headers: 6 + Size of section headers: 64 (bytes) + Number of section headers: 50 + Section header string table index: 47 +``` + + + + + +下面是根据浅显的学习,写的从data段中读取全局变量的代码 + +```c +#include +#include +#include +#include +#include + +char master_key[16] = "0123456789abcdef"; + +int main(int argc, char *argv[]) { + FILE *fp; + Elf64_Ehdr elf_header; + Elf64_Shdr *p_elf_section_header; + char *p_section_str = NULL; + int i = 0; + char *section_name = NULL; + + fp = fopen("./master_key", "r"); + if (fp == NULL) + exit(-1); + + fread(&elf_header, sizeof(elf_header), 1, fp); + + + printf("e_shoff = %ld, e_shentsize = %d, e_shnum = %d, e_shstrndx = %d\n", elf_header.e_shoff, elf_header.e_shentsize, elf_header.e_shnum, elf_header.e_shstrndx); + + p_elf_section_header = malloc(sizeof(Elf64_Shdr) * elf_header.e_shnum); + fseek(fp, elf_header.e_shoff, SEEK_SET); + fread(p_elf_section_header, sizeof(Elf64_Shdr) * elf_header.e_shnum, 1, fp); + + + p_section_str = malloc(p_elf_section_header[elf_header.e_shstrndx].sh_size); + fseek(fp, p_elf_section_header[elf_header.e_shstrndx].sh_offset, SEEK_SET); + fread(p_section_str, p_elf_section_header[elf_header.e_shstrndx].sh_size, 1, fp); + + + for (i = 0; i < elf_header.e_shnum; i++) { + printf("sh_name = %d\n", p_elf_section_header[i].sh_name); + section_name = p_section_str + p_elf_section_header[i].sh_name; + + printf("section name = %s\n", section_name); + + if (strcmp(section_name, ".data") == 0) { + break; + } + } + + Elf64_Off data_offset = p_elf_section_header[i].sh_offset; + Elf64_Xword data_size = p_elf_section_header[i].sh_size; + + char * data_content = malloc(data_size); + fseek(fp, data_offset, SEEK_SET); + fread(data_content, data_size, 1, fp); + + int j = 0; + for (j = 16; j < data_size; j++) { + printf("%02x ", data_content[j]); + } + printf("\n"); + + free(data_content); + free(p_section_str); + free(p_elf_section_header); + + fclose(fp); + + printf("Hello, World!\n"); + + return 0; +} + +``` + + +``` + +`Linux/Linux开发/LinuxPam模块.md`: + +```md +# Linux中Pam模块 + +## pam简介 + +`Linux-PAM`(Linux可插入认证模块)是一套共享库,使本地系统管理员可以随意选择程序的认证方式。换句话说,不用重新编译一个包含`PAM`模块功能的应用程序,就可以改变它使用的认证机制。这种方式下,就算升级本地认证机制,也不用修改程序。`PAM`使用配置`/etc/pam.d/`下的文件,来管理对程序的认证方式。应用程序调用相应的配置文件,从而调用本地的认证模块。 + +认证模块放置在`/lib/security`目录下,以加载库的形式存在。 + +像我们使用`su`命令时,系统会提示输入`root`用户密码,这就是`su`命令通过调用`PAM`模块实现的。 + +![image-20210611153222171](https://raw.githubusercontent.com/supermanc88/ImageSources/master/20210611173939.png) + + + +## pam的配置文件 + +以`sshd`为例: + +![image-20210611153412163](https://raw.githubusercontent.com/supermanc88/ImageSources/master/20210611173954.png) + + + +### PAM模块类型 + +PAM模块有四种类型,分别代表四种不同的任务: + +- 认证管理(`auth`):认证模块接口,如验证用户身份、检查密码是否可以通过,并设置用户凭据 +- 账号管理(`account`):账户模块接口,检查指定账户是否满足当前验证条件,如用户是否有权限访问所请求的服务,检查账户是否到期 +- 会话管理(`session`):会话模块接口,用于管理和配置用户会话。会话在用户成功认证之后启动生效 +- 密码管理(`password`):密码模块接口,用于更改用户密码,以及强制使用强密码配置 + +一个类型可能有多行,它们按顺序依次由PAM模块调用。 + +单个`PAM`库模块可以提供给任何或所有模块接口使用。例如,`pam_unix.so`提供给四个模块接口使用。 + + + +### PAM控制标志 + +所有的PAM模块被调用时都会返回成功或者失败的结果,每个PAM模块中由多个对应的`控制标志`决定结果是否对过或失败。每一个控制标志对应一个处理结果,**PAM库将这些成功[或失败]的结果整合为一个整体的成功[或失败]结果**,然后将结果返回给应用程序。模块可以按特定的顺序堆叠。`控制标志`是实现用户在对某一个特定的应用程序或服务身份验证的具体实现细节。该控制标志是PAM配置文件中的第二个字段,`PAM`控制标志如下: + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/20210611174002.png) + +- `required` **表示该行以及所涉及模块的成功是用户通过鉴别的必要条件。换句话说,只有当对应于应用程序的所有带 required标记的模块全部成功后,该程序才能通过鉴别。**同时,如果任何带required标记的模块出现了错误,PAM并不立刻将错误消息返回给应用程序,而是在所有模块都调用完毕后才将错误消息返回调用他的程序。 反正说白了,就是**必须将所有的模块都执行一次,其中任何一个模块验证出错,验证都会继续进行,并在执行完成之后才返回错误信息。这样做的目的就是不让用户知道自己被哪个模块拒绝,通过一种隐蔽的方式来保护系统服务。**就像设置防火墙规则的时候将拒绝类的规则都设置为drop一样,以致于用户在访问网络不成功的时候无法准确判断到底是被拒绝还是目标网络不可达。 + +- `requisite` 模块结果必须**成功才能继续认证**,如果在此处测试**失败,则会立即将失败结果通知给用户**。 + +- `sufficient` 模块结果如果**测试失败,将被忽略**。如果`sufficient`模块测试成功,并且之前的`required`模块没有发生故障,`PAM`会向应用程序返回通过的结果,不会再调用堆栈中其他模块。表示该行以及所涉及模块验证成功是用户通过鉴别的充分条件。也就是说**只要标记为`sufficient`的模块一旦验证成功,那么`PAM`便立即向应用程序返回成功结果而不必尝试任何其他模块**。即便后面的层叠模块使用了`requisite`或者`required`控制标志也是一样。**当标记为sufficient的模块失败时,sufficient模块会当做 optional对待。因此拥有sufficient 标志位的配置项在执行验证出错的时候并不会导致整个验证失败,但执行验证成功之时则大门敞开。所以该控制位的使用务必慎重。** + +- `optional` 该模块返回的通过/失败**结果被忽略**。**当没有其他模块被引用时,标记为optional模块并且成功验证时该模块才是必须的**。该模块被调用来执行一些操作,并不影响模块堆栈的结果。表示即便该行所涉及的模块验证失败用户仍能通过认证。在`PAM`体系中,带有该标记的模块失败后将继续处理下一模块。也就是说即使本行指定的模块验证失败,也允许用户享受应用程序提供的服务。使用该标志,`PAM`框架会忽略这个模块产生的验证错误,继续顺序执行下一个层叠模块。 + +- `include` 与其他控制标志不同,**include与模块结果的处理方式无关**。该标志用于直接引用其他`PAM`模块的配置参数。表示在验证过程中调用其他的PAM配置文件。在`RHEL`系统中有相当多的应用通过完整调用`/etc/pam.d/system-auth`来实现认证而不需要重新逐一去写配置项。这也就意味着在很多时候只要用户能够登录系统,针对绝大多数的应用程序也能同时通过认证。 + +- `substack` 从为此标志指定的配置文件中包含给定类型的所有行。 运行其他配置文件中的流程,并将整个运行结果作为该行的结果进行输出。该模式和 `include` 的不同点在于认证结果的作用域:如果某个流程栈 `include` 了一个带 `requisite` 的栈,这个 `requisite` 失败将直接导致认证失败,同时退出栈;而某个流程栈 `substack` 了同样的栈时,`requisite` 的失败只会导致这个子栈返回失败信号,母栈并不会在此退出。 + + + +另外还有一种比较复杂的格式为`value = action`的语法来设置控制标志,标志之间会以空格分开。格式如下: + +```shell +value1 = action1 value2 = action2 …… +``` + +其中value可以是下列Linux PAM库的返回值: +`success`、`open_err`、`symbol_err`、`service_err`、 `system_err`、`buf_err`、`perm_denied`、`auth_err`、`cred_insufficient`、`authinfo_unavail`、`user_unknown`、`maxtries`、`new_authtok_reqd`、`acct_expired`、 `session_err`、`cred_unavail`、`cred_expired`、`cred_err`、`no_module_data`、`conv_err`、 `authtok_err`、`authtok_recover_err`、`authtok_lock_busy`、`authtok_disable_aging`、 `try_again`、`ignore`、`abort`、`authtok_expired`、`module_unknown`、`bad_item`和`default`。 + +最后一个(`default`)能够用来设置上面的返回值无法表达的行为。 + +actionN可以是一个非负整数或者是下面的记号之一:`ignore`、`ok`、`done`、`bad`、`die`和`reset`。如果是非负整数J,就表示需要忽略后面J个同样类型的模块。通过这种方式,系统管理者可以更加灵活地设置层叠模块,模块的层叠路径由单个模块的反应决定。 + +**关于这几个记号的详细解释:** + +- `ignore`:如果使用层叠模块,那么这个模块的返回值将被忽略,不会被应用程序知道。 +- `bad`:他表示这个返回码应该被看作是模块验证失败的标志。如果这个模块是层叠模块的第一个验证失败的模块,那么他的状态值就是整个层叠模块验证的状态值和结果。 +- `die`:终止层叠模块验证过程,立刻返回到应用程序。 +- `ok`:告诉PAM这个模块的**返回值将直接作为所有层叠模块的返回值**。也就是说,如果这个模块前面的模块返回状态是`PAM_SUCCESS`,那这个返回值就会覆盖前面的返回状态。注意:如果前面的模块的返回状态表示模块验证失败,那么不能使用这个返回值再加以覆盖。 +- `done`:终止后续层叠模块的验证,把控制权立刻交回应用程序。 +- `reset`:清除所有层叠模块的返回状态,从下一个层叠模块重新开始验证。 + + + +### PAM模块路径 + +模块路径.即要调用模块的位置. 如果是64位系统,一般保存在/lib64/security,如: pam_unix.so,同一个模块,可以出现在不同的类型中.它在不同的类型中所执行的操作都不相同.这是由于每个模块,针对不同的模块类型,编制了不同的执行函数。 + + + +### PAM模块参数 + +模块参数,即传递给模块的参数.参数可以有多个,之间用空格分隔开,如:`password required pam_unix.so nullok obscure min=4 max=8 md5`。 + + + +### PAM配置方法 + +可以使用 `man` 命令查看配置,比如要查找某个程序支持PAM模块的配置,可以使用 `man` 加模块名(去掉.so)查找说明。 + +如 `man pam_unix`。(模块名可以在目录`/lib/security/`或`/lib64/security/`中找到。) + + + +### 例子 + +环境:CentOS7 1708 + +`/etc/pam.d/system-auth-ac` + +![image-20210611172650862](https://raw.githubusercontent.com/supermanc88/ImageSources/master/20210611174009.png) + + + +第一部分表示,当用户登录的时候,首先会通过auth类接口对用户身份进行识别和密码认证。所以在该过程中验证会经过几个带auth的配置项。 + +其中的第一步是通过`pam_env.so`模块来定义用户登录之后的环境变量, pam_env.so允许设置和更改用户登录时候的环境变量,默认情况下,若没有特别指定配置文件,将依据`/etc/security/pam_env.conf`进行用户登录之后环境变量的设置。 + +然后通过pam_unix.so模块来提示用户输入密码,并将用户密码与/etc/shadow中记录的密码信息进行对比,如果密码比对结果正确则允许用户登录,而且该配置项的使用的是“sufficient”控制位,即表示只要该配置项的验证通过,用户即可完全通过认证而不用再去走下面的认证项。不过在特殊情况下,用户允许使用空密码登录系统,例如当将某个用户在/etc/shadow中的密码字段删除之后,该用户可以只输入用户名直接登录系统。 + +下面的配置项中,通过pam_succeed_if.so对用户的登录条件做一些限制,表示允许uid大于500的用户在通过密码验证的情况下登录,在Linux系统中,一般系统用户的uid都在500之内,所以该项即表示允许使用useradd命令以及默认选项建立的普通用户直接由本地控制台登录系统。 + +最后通过pam_deny.so模块对所有不满足上述任意条件的登录请求直接拒绝,pam_deny.so是一个特殊的模块,该模块返回值永远为否,类似于大多数安全机制的配置准则,在所有认证规则走完之后,对不匹配任何规则的请求直接拒绝。 + + + +第二部分的三个配置项主要表示通过account账户类接口来识别账户的合法性以及登录权限。 + +第一行仍然使用pam_unix.so模块来声明用户需要通过密码认证。第二行承认了系统中uid小于500的系统用户的合法性。之后对所有类型的用户登录请求都开放控制台。 + + + +第三部分会通过password口另类接口来确认用户使用的密码或者口令的合法性。第一行配置项表示需要的情况下将调用pam_cracklib来验证用户密码复杂度。如果用户输入密码不满足复杂度要求或者密码错,最多将在三次这种错误之后直接返回密码错误的提示,否则期间任何一次正确的密码验证都允许登录。需要指出的是,pam_cracklib.so是一个常用的控制密码复杂度的pam模块,关于其用法举例我们会在之后详细介绍。之后带pam_unix.so和pam_deny.so的两行配置项的意思与之前类似。都表示需要通过密码认证并对不符合上述任何配置项要求的登录请求直接予以拒绝。不过用户如果执行的操作是单纯的登录,则这部分配置是不起作用的。 + + + +第四部分主要将通过session会话类接口为用户初始化会话连接。其中几个比较重要的地方包括,使用pam_keyinit.so表示当用户登录的时候为其建立相应的密钥环,并在用户登出的时候予以撤销。不过该行配置的控制位使用的是optional,表示这并非必要条件。之后通过pam_limits.so限制用户登录时的会话连接资源,相关pam_limit.so配置文件是/etc/security/limits.conf,默认情况下对每个登录用户都没有限制。关于该模块的配置方法在后面也会详细介绍。 + + + +### 常用PAM模块介绍 + +| PAM模块 | 管理类型 | 说明 | +| ---------------- | -------------------------------- | ------------------------------------------------------------ | +| pam_unix.so | auth | 提示用户输入密码,并与/etc/shadow文件相比对.匹配返回0 | +| pam_unix.so | account | 检查用户的账号信息(包括是否过期等).帐号可用时,返回0. | +| pam_unix.so | password | 修改用户的密码. 将用户输入的密码,作为用户的新密码更新shadow文件 | +| pam_shells.so | auth,account | 如果用户想登录系统,那么它的shell必须是在/etc/shells文件中 | +| pam_deny.so | account,auth,password,session | 该模块可用于拒绝访问 | +| pam_deny.so | account,auth,password,session | 模块任何时候都返回成功 | +| pam_securetty.so | auth | 如果用户要以root登录时,则登录的tty必须在/etc/securetty之中 | +| pam_listfile.so | account,auth,password,session | 访问应用程的控制开关 | +| pam_cracklib.so | password | 这个模块可以插入到一个程序的密码栈中,用于检查密码的强度 | +| pam_limits.so | session | 定义使用系统资源的上限,root用户也会受此限制,可以通过/etc/security/limits.conf或/etc/security/limits.d/*.conf来设定 | + + + + + + +``` + +`Linux/Linux开发/Linux内核编译全记录.md`: + +```md +# Linux 内核编译全记录 + +环境:Ubuntu 16.04 +内核版本: 4.15.0-88-generic + + +## 下载源码 + +由于是第一次编译内核,就找一个和目前版本相近的版本代码来编译 + +在[kernel](www.kernel.org)下载源码,我这里找的是4.14.172版本 + +将源码放置到`/usr/src`目录下,并解压 + +![image](https://raw.githubusercontent.com/supermanc88/ImageSources/master/1583388103(1).jpg) + +创建一个软链接 + +![image](https://raw.githubusercontent.com/supermanc88/ImageSources/master/1583388683(1).jpg) + + +## 配置 + +Linux内核有多种方式去编译: + +- make config (基于文本的最为传统的配置界面,不推荐使用) + +- make menuconfig (基于文本菜单的配置界面) + +- make xconfig (要求了QT开发环境) + +- make kconfig (KDE桌面环境,并且安装了QT开发环境) + +- make gconfig (gnome桌面环境,并且安装了QT开发环境) + + + +一般都是用`make menuconfig`的方式去编译 + +如下所示,选择配置: + +![image](https://raw.githubusercontent.com/supermanc88/ImageSources/master/1583389138(1).jpg) + +目前来看,我什么也不知道,就先按照默认的来编译吧,选择save并退出 + +### 内核调试选项 + +选择 `Kernel hacking`中的内容: + +``` +Kernel hacking---> + -*- Magic SysRq key + [*] Kernel debugging + [*] Compile the kernel with debug info + [*] KGDB: kernel debugging with remote gdb ---> + <*> KGDB: use kgdb over the serial console +``` + + +## 编译 + +在配置完成后,就可以编译了,我使用的是虚拟机,给了两个核心: + +``` +make -j2 # 其中的2是编译时所使用的线程数 +``` + +编译过程中出现一个错误 + +![image](https://raw.githubusercontent.com/supermanc88/ImageSources/master/1583396226(1).jpg) + +原因是虚拟机20G的大小全给用完了,可能是我别的软件装大多的原因,虚拟机关机扩容 + + +扩容完毕之后,重新make编译 + +50分钟过去,编成功了: + +![image](https://raw.githubusercontent.com/supermanc88/ImageSources/master/1583392301(1).jpg) + + +## 安装 + +### 安装模块: + +``` +make modules_install +``` + +安装完成后,会在`/lib/modules`目录下生成对应版本的目录 + +![image](https://raw.githubusercontent.com/supermanc88/ImageSources/master/1584632963(1).jpg) + + +### 内核安装 + +这是在内核install之前的情况: + +建议备份initrd和vmlinuz,虽然他们会自己备份。 + +![image](https://raw.githubusercontent.com/supermanc88/ImageSources/master/1584633211(1).png) + +再看看源码目录,多出了个vmlinux文件: + +![image](https://raw.githubusercontent.com/supermanc88/ImageSources/master/1583396589(1).jpg) + + +``` +make install +``` + +执行make install,将会拷贝源码目录下的vmlinux到/boot/目录并压缩为vmlinuz,并创建initrd + +安装完成后会在`/boot/`目录下生成对应版本的目录 + +![image](https://raw.githubusercontent.com/supermanc88/ImageSources/master/1583396860(1).jpg) + +![image](https://raw.githubusercontent.com/supermanc88/ImageSources/master/1584633351(1).png) + +一切没问题后,查看一下`/boot/grub/grub.conf` + +![image](https://raw.githubusercontent.com/supermanc88/ImageSources/master/1584634467(1).jpg) + + +修改`/etc/default/grub` + +注释掉 `GRUB_HIDDEN_TIMEOUT` + +![image](https://raw.githubusercontent.com/supermanc88/ImageSources/master/1583398656(1).jpg) + + +修改之后,必须运行: + +``` +sudo update-grub +``` + +#### 为KGDB内核创建新的启动项 + +新增的KGDB启动项,与原始启动项相比只增加了一个参数:`kgdboc=ttyS0,11520` + +如果需要目标机一启动就断住(比如要调试启动阶段的代码),则再增加一个参数`kgdbwait` + +![image](https://raw.githubusercontent.com/supermanc88/ImageSources/master/1583397078(1).jpg) + +修改之后,必须运行: + +``` +sudo update-grub +``` + +修改已更新: + +![image](https://raw.githubusercontent.com/supermanc88/ImageSources/master/v2-8b728aaa77aefedc1a51797525486c17_720w.jpg) + +重启系统时就可以选择内核了 + + +## 内核调试虚拟机配置 + +### 调试环境一 + +主机:manjaro + +被调试机:虚拟机ubuntu + +虚拟机配置串口: + +![image](https://raw.githubusercontent.com/supermanc88/ImageSources/master/1584634592(1).png) + +自定义名为: `/tmp/isocket` + +其中/tmp/isocket是一个文件,主机和虚拟机利用这个文件进行串口通信。 + +主机需要两个程序socat和telnet + +在主机terminal里: + +```shell +socat /tmp/isocket tcp-listen:9001 & +telnet 127.0.0.1 9001 +``` + +在虚拟机上: + +```shell +cat /dev/ttyS0 +``` + +此时主机terminal里输入信息虚拟机可以通过ttyS0接收到。 + + +#echo ‘hello’ > /dev/ttyS0 + +主机的terminal可以收到 + +telnet输入Ctrl+]结束 + +如果串口收发没有问题,可以继续。 + +然后在主机terminal中 + +```shell +socat /tmp/isocket tcp-listen:9001 & + +cd + +gdb vmlinux + +(gdb) target remote:9001 +``` + +正常情况下就可以调试了 + +在这里输入c就会把控制权交还给虚拟机,当我们想把控制权交还给主机的gdb是,在虚拟机的terminal中输入 + +```shell +echo g>/proc/sysrq-trigger +``` + + +### 调试环境二 + +主机:虚拟机ubuntu 最好使用14.0.4 高版本的编译的时候没有frame pointer选项,调试一直有问题,断点打不上 + +被调试机:虚拟机buuntu + +两个虚拟机为编译出内核后,克隆而来 + + +主机配置: + +![image](https://raw.githubusercontent.com/supermanc88/ImageSources/master/1584779659.png) + +该端是客户端 + +被调试机配置: + +![image](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200812115534546.png) + +该端是服务器 + +开机之后,被调试机和之前一样进入等待调试阶段, + + + +```sh +echo g>/proc/sysrq-trigger +``` + + + +主机使用如下命令: + +``` +gdb --baud 11520 vmlinux +target remote /dev/ttyS0 +``` + +开始调试。 + + + + + +### CentOS安装的debuginfo + + + +路径如下: + + + +![image-20200812115534546](https://raw.githubusercontent.com/supermanc88/ImageSources/master/1584779604(1).png) + + + +centos系统中debuginfo查找地址: + +http://debuginfo.centos.org/ +``` + +`Linux/Linux开发/Linux命令行参数处理.md`: + +```md +# Linux命令行参数处理 + +## getopt + +```c + #include + + int getopt(int argc, char * const argv[], + const char *optstring); + + extern char *optarg; + extern int optind, opterr, optopt; +``` + +### 描述 + +getopt解析命令行参数。它的参数argc和argv是在程序调用时传递给main()函数的参数计数和数组。 + +参数argc它是程序启动时传递给main()函数的参数。argc为传入程序的参数的个数,默认为1,代表程序自身。 + +参数argv是程序启动时传递给main()函数的参数。argv是一个指针数组,用来存放命令行参数。传入的命令行参数以空白符(包括空格符,制表符,换行符)分界,并以字符串的形式存放在指针数组argv中,argv数组的每个元素存放一个字符串指针,指向一个传入的命令行参数。 + +以`-`(而不是"_“)开头的argv元素就是一个**选项元素**。“-”叫做**选项标识符**。选项标识符后面可以紧跟一个字符,这个字符叫做**选项字符**。选项字符后面可以紧跟一个或多个字符,这些字符叫做**选项参数**。 + +也就是说一个选项元素包括选项标识符、选项字符、选项参数三部分,其中选项字符和选项的参数是可选的,如果有选项字符则必须紧跟在标识符后,选项参数根据规则不同可以紧跟在标识符后面,也可以与选项字符用空白符分开。如:命令行"app.exe -abc -d",此时argc为3,argv[0]为“app.exe”,**argv[1] 为 “-abc”**,argv[2] 为 “-d”。**其中“a”是第一个选项字符,“bc”为选项参数**,“d”是第二选项字符,没有选项参数。**如果重复调用getopt(),它依次返回每个选项元素的选项字符。在前面的例子中第一次返回“a”,第二次返回“d”。** + + +**默认情况下,getopt()在每次调用时会重新排列argv数组中除了argv[0]之外的元素,最终这些元素中所有既不是选项(无论合不合法)也不是选项参数的元素都会排到argv数组末尾。**这样argv数组中除argv[0]之外的元素分为与选项有关和与选项无关两类。但类内元素的相对顺序并不会变。 + + + +### 规则 + +- 如果选项字符后跟一个冒号,就表示这个选项后面必须带有参数(没有带选项参数会报错,然后将optarg置为null,optopt置为该选项字符,且返回’?’),getopt将指向同一个选项元素中的选项参数或下一个argv元素的指针放入optarg中,也就是说这个参数可以和选项连在一起写(此时optarg指向同一个同一个选项元素中的选项参数),也可以用空白符隔开(此时optarg指向下一个argv元素),比如app.exe -a123和app.exe -a 123(中间有空格)都表示123是-a的参数。特别地,如果漏写选项参数,且它不是最后一个argv元素,则无论下一个元素是什么都将视为该选项字符的选项参数。 +- **如果选项字符后跟两个冒号**,就表示这个选项可以有参数,也可以没有参数,但要注意有参数时,**选项字符与选项参数之间不能有空格**(若有空格,则认为该选项不带参数),这一点和一个冒号时是有区别的。这是一个GNU才有的扩展功能。 +- **不带冒号**的表示这个选项没有参数。如果命令行参数中此选项字符之后紧跟其他字符,则会被作为选项字符处理。 +``` + +`Linux/Linux开发/Linux回收子进程.md`: + +```md +# Linux回收子进程 + +## 孤儿进程 + +父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init进程,称为init进程领养孤儿进程。 + + + +## 僵尸进程 + +子进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。 + +父进程通过PCB给子进程报仇。 + + + +另辟蹊径: + +连续使用两次fork避免僵尸进程。 + +![image-20200616171551843](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200616171551843.png) + + + +> fork出子进程后,子进程马上fork出孙进程,子进程马上关闭,那么孙进程就会被`init`进程领养,就不会存在僵尸进程了。 + +### wait函数 + +功能: + +1. 阻塞等待子进程能出 +2. 回收子进程残留资源 +3. 获取子进程结束状态(退出原因,在Linux中所有的异常退出都是因为收到信号 ) + +查看子进程结束状态: + +1. WIFEXITED 为非0 进程正常结束 +2. WEXITSTATUS 如上宏为真,使用此宏,获取进程退出状态(exit的参数) +3. WIFSIGNALED 为非0 进程异常终止 +4. WTERMSIG 如上宏为真,使用此宏,获取进程终止的那个信号 + + + +如果有多个子进程,哪个子进程先死,wait就等到哪个进程。 + + + +### waitpid函数 + +作用同wait,但可指定PID进程清理,可以不阻塞。 + +```c + #include + + pid_t waitpid(pid_t pid, int *stat_loc, int options); +``` + +参数pid: + +1. \>0 回收指定子进程 +2. =-1 回收任意子进程 +3. =0 回收和当前调用waitpid同一组的所有子进程 +4. <-1 回收指定进程组的任意子进程 + +`options`可以指定非阻塞`WNOHANG` + + + + + +wait和waitpid每次调用只能回收一个子进程 +``` + +`Linux/Linux开发/Linux执行shell命令并获取输出.md`: + +```md +# Linux执行shell命令并获取输出 + +关键函数:popen pclose + +popen通过创建一个管道,将shell命名传给`/bin/sh`加`-c`参数执行,执行成功后,返回一个打开的文件流。 + + + +```c +#include +#include + +void execute_commond(char *cmd, char *result) +{ + char ps[1024] = {0}; + char buf_result[1024] = {0}; + + FILE *ptr; + + strcpy(ps, cmd); + + + if ((ptr = popen(ps, "r")) != NULL) { + while (fgets(buf_result, 1024, ptr) != NULL) { + strcat(result, buf_result); + if (strlen(result) > 1024) + break; + } + + pclose(ptr); + } else { + printf("popen error\n"); + } + +} + + +int main() +{ + char result[1024] = {0}; + + execute_commond("dmidecode -s system-uuid", result); + + printf("%s\n", result); + + return 0; + +} + +``` + + +``` + +`Linux/Linux开发/Linux文件描述符.md`: + +```md +# Linux文件描述符表 + +对于内核而言,所有打开的文件都通过`文件描述符`引用。文件描述符是一个非负整数。当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。 + + + +> Unix系统shell把文件描述符0与进程的标准输入关联,文件描述符1与标准输出关联,文件描述符2与标准错误关联。 + +STDIN_FILENO/STDOUT_FILENO/STDERR_FILENO分别代表文件描述符0、1、2。 + + + +文件描述符的变化范围是0-OPEN_MAX-1。也就是说允许每个进程最多打开这么多个文件。 + + + +不用管这个文件描述符用0、1、2、3。。。这些数字很简单,其实这些数字在内部都对应着不同的指针,这些指针又指向一个文件结构或一个文件节点。 + + + +## /dev/fd/ + +在/dev/fd目录下,其目录项是名为0、1、2等的文件。打开文件/dev/fd/n等效于复制描述符n + + + +如: + +```c +fd = open("/dev/fd/0", mode); + +// 等同 + +fd = dup(0); +``` + +一般情况下,参数mode在大部分系统下被忽略,而另外一些系统要求mode必须是所引用的文件**初始打开时所使用的打开模式的一个子集**。 + + + +![image-20200606172438112](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200606172438112.png) +``` + +`Linux/Linux开发/Linux系统调用.md`: + +```md +# Linux系统调用分析 + + +以 linux-2.6.32.69 源码分析 + + +``` +#define __SYSCALL(nr, sym) extern asmlinkage void sym(void) ; + + +typedef void (*sys_call_ptr_t)(void); + +const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = { + /* + *Smells like a like a compiler bug -- it doesn't work + *when the & below is removed. + */ + [0 ... __NR_syscall_max] = &sys_ni_syscall, +#include +}; +``` + +``` +asmlinkage long sys_ni_syscall(void) +{ + return -ENOSYS; +} +``` + +上面的代码就是给sys_call_table设置一个初始化的值,这个函数指针指向一个返回没有系统调用的函数。 + + +可以看到`sys_call_table`中存放的是函数指针。 + +`unistd_64.h`文件中的代码如下: + +``` +#define __NR_read 0 +__SYSCALL(__NR_read, sys_read) +#define __NR_write 1 +__SYSCALL(__NR_write, sys_write) +#define __NR_open 2 +__SYSCALL(__NR_open, sys_open) +#define __NR_close 3 +__SYSCALL(__NR_close, sys_close) +#define __NR_stat 4 +__SYSCALL(__NR_stat, sys_newstat) +#define __NR_fstat 5 +__SYSCALL(__NR_fstat, sys_newfstat) +#define __NR_lstat 6 +__SYSCALL(__NR_lstat, sys_newlstat) +#define __NR_poll 7 +__SYSCALL(__NR_poll, sys_poll) + +#define __NR_lseek 8 +__SYSCALL(__NR_lseek, sys_lseek) +#define __NR_mmap 9 +__SYSCALL(__NR_mmap, sys_mmap) +... +``` +``` + +`Linux/Linux开发/Linux进程开发.md`: + +```md +# Linux进程 + +## 进程的基本状态 + +有五种: + +1. 初始态 +2. 就绪态 +3. 运行态 +4. 挂起态 +5. 终止态 + + + +其中初始态与就绪态常结合来看。 + +![image-20200528151830099](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200528151830099.png) + + + +## 环境变量 + +环境变量以进程为单位,每个进程的环境变量可能都不一样。 + + + +```c +extern char ** environ; +``` + + + + + +## fork 函数 + +返回值:有两个返回值 + +1. 返回子进程的pid(非负整数) +2. 返回0 + +![image-20200528164143353](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200528164143353.png) + +如上图所示,当fork执行后,会有子进程出现,fork函数之上的代码在子进程中不再运行,现在子进程中也有一个fork函数,返回值是什么呢? + +父进程返回子进程PID + +子进程返回0表示fork成功执行 + + + +在之后的代码中,可根据fork的返回值,用来判断是在父进程中还是在子进程中。 + + + +![image-20200528165826971](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200528165826971.png) + + + +## 父子进程共享 + +父子进程在fork之后,有哪些是相同的,哪些是不同的? + +相同处:全局变量、.data、.text、栈、堆、环境变量、用户ID、宿主目录、进程工作目录、信号处理方式。。。(但不可共享,是独立的进程) + +不同处:进程ID、fork返回值、进程运行时间、父进程ID、闹钟(定时器)、未决信号集 + + + +父子进程间遵循**读时共享写时复制(共享同一块物理地址)**的原则。这样设计,无论子进程执行父进程的逻辑还是执行自己的逻辑都能节省内存开销。 + +父子进程共享: + +1. 文件描述符(打开文件的结构体) +2. mmap建立的映射区(进程间通信) +``` + +`Linux/Linux开发/Linux进程间通信.md`: + +```md +# Linux进程间通信(IPC) + +IPC:InterProcess Communication + + + +## Linux下的文件类型 + +- \- 文件 +- d 目录 +- l 符号链接 +- s 套接字 +- b 块设备 +- c 字符设备 +- p 管道 + + + +文件、目录、符号链接是占用磁盘存储的。其它的不占用,其它的统一称为`伪文件` + + + +## 管道 + +```c + #include + + int pipe(int fildes[2]); +``` + +返回两个文件描述符:fd[0]为读而打开,fd[1]为写而打开 + + + +1. 其本质是一个伪文件(实为内核缓冲区) +2. 由两个文件描述符引用,一个表示读端,一个表示写端 +3. 规定数据从管道的写端流入管道,从读端流出 + +管道的原理:管道实为内核使用环形队列机制,借助内核缓冲区(4K)实现 + + + +### 管道的局限性 + +1. 双向半双工,因此,数据只能在一个方向上流动 +2. 只能在有公共祖先的进程间使用管道 +3. 数据自己读不能自己写 +4. 数据一旦被读走,便不在管道中存在,不可反复读取 + + + +## FIFO(有名管道) + + + +### 非阻塞标志(O_NONBLOCK): + +- 阻塞模式:只读open要阻塞到某个进程以只写方式打开此FIFO,只写open要阻塞到某个进程以只读方式打开此FIFO; + +- 非阻塞模式:只读立即返回,如果没有进程以只读方式打开FIFO,则只写open返回-1,erron=ENXIO; + + + +### 一端关闭 + +- 若读一个已经关闭写端的FIFO,则读取完数据后,会读到文件结束符,read返回0; + +- 若写一个已经关闭读端的FIFO,则产生SIGPIPE; + + + +代码详见:code/fifo + +## 共享内存 + + + + +``` + +`Linux/Linux开发/Linux驱动模块的依赖.md`: + +```md +# Linux驱动模块的依赖 + +内核模块如果引用到Linux内核中的符号,这个则不属于模块间的依赖,因为内核导出的符号本身就是供内核模块所使用。 + +本帖要讨论的是在两个独立编译的模块A和B之间,B如果要引用A导出的符号,在Makefile中该如何把这一信息加入的问题。 + +绝大多数情形下,内核配置时能会启用`CONFIG_MODVERSIONS`,这意味着无论是内核还是内核模块,在导出符号时都会为该符号生成CRC校验码,这个校验码保存在`Module.symvers`文件中。 + +最常见的是,模块会引用到内核导出的符号,此时模块的Makefile没有什么特殊的地方。现在假设A导出一个符号`A_sym`,那么`A_sym`的CRC校验码会存在于A模块所在目录的`Module.symvers`文件中,如果B模块引用到A模块的`A_sym`符号,那么是需要在它的`'__versions' section`中生成`A_sym`符号的校验码的,这个校验码直接取自于A模块的`Module.symvers`文件。如果B模块在编译时从它的 Makefile中无法获得这一信息,首先编译阶段就会产生一个WARNING,其次加载阶段也会因为符号没有CRC校验码而导致加载失败。 + +此时我们需要在B模块的Makefile文件中加上下面一行,以告诉模块的编译工具链到何处查找`A_sym`符号的CRC校验码: + +KBUILD_EXTMOD := A模块所在的目录 + +如此,modpost工具除了到内核所在目录下查找外,还会到KBUILD_EXTMOD指定的目录下查找Module.symvers,以确定本模块所有未定义符号的CRC值。 + +最后给一个具体的Makefile: + +``` +obj-m := dep_on_A.o +KERNELDIR := /lib/modules/$(shell uname -r)/build +KBUILD_EXTMOD := /home/dennis/workspace/Linux/book/kmodule/A_mod +PWD := $(shell pwd) +default: + $(MAKE) -C $(KERNELDIR) M=$(PWD) modules +``` + +## 内核模块导出符号表 + +示例: + +首先编写一个导出函数的模块 module_A: test_module_A.c + +``` +#include +#include +#include + +MODULE_LICENSE("Dual BSD/GPL"); + +static int test_export_A_init(void) +{ + /* 进入内核模块 */ + printk(KERN_ALERT "*************************\n"); + printk(KERN_ALERT "ENTRY test_export_A!\n"); + printk(KERN_ALERT "*************************\n"); + printk(KERN_ALERT "\n\n\n\n\n"); + return 0; +} + +static void test_export_A_exit(void) +{ + /* 退出内核模块 */ + printk(KERN_ALERT "*************************\n"); + printk(KERN_ALERT "EXIT test_export_A!\n"); + printk(KERN_ALERT "*************************\n"); + printk(KERN_ALERT "\n\n\n\n\n"); +} + +/* 要导出的函数 */ +int export_add10(int param) +{ + printk(KERN_ALERT "param from other module is : %d\n", param); + return param + 10; +} +EXPORT_SYMBOL(export_add10); + +module_init(test_export_A_init); +module_exit(test_export_A_exit); +``` + +test_module_A.c 的 Makefile + +``` +# must complile on customize kernel +obj-m += export_A.o +export_A-objs := test_export_A.o + +#generate the path +CURRENT_PATH:=$(shell pwd) +#the current kernel version number +LINUX_KERNEL:=$(shell uname -r) +#the absolute path +LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL) +#complie object +all: + make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules + rm -rf modules.order .*.cmd *.o *.mod.c .tmp_versions *.unsigned +#clean +clean: + rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c *.ko .tmp_versions *.unsigned +``` + +再编写一个内核模块 module_B,使用 module_A 导出的函数 : test_module_B.c + +``` +#include +#include +#include + +MODULE_LICENSE("Dual BSD/GPL"); + +extern int export_add10(int); // 这个函数是 module_A 中实现的 + +static int test_export_B_init(void) +{ + /* 进入内核模块 */ + printk(KERN_ALERT "*************************\n"); + printk(KERN_ALERT "ENTRY test_export_B!\n"); + printk(KERN_ALERT "*************************\n"); + printk(KERN_ALERT "\n\n\n\n\n"); + + /* 调用 module_A 导出的函数 */ + printk(KERN_ALERT "result from test_export_A: %d\n", export_add10(100)); + + return 0; +} + +static void test_export_B_exit(void) +{ + /* 退出内核模块 */ + printk(KERN_ALERT "*************************\n"); + printk(KERN_ALERT "EXIT test_export_B!\n"); + printk(KERN_ALERT "*************************\n"); + printk(KERN_ALERT "\n\n\n\n\n"); +} + +module_init(test_export_B_init); +module_exit(test_export_B_exit); +``` + +test_module_B.c 的 Makefile + +``` +# must complile on customize kernel +obj-m += export_B.o +export_B-objs := test_export_B.o + +#generate the path +CURRENT_PATH:=$(shell pwd) +#the current kernel version number +LINUX_KERNEL:=$(shell uname -r) +#the absolute path +LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL) +#complie object +all: + make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules + rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c .tmp_versions *.unsigned +#clean +clean: + rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c *.ko .tmp_versions *.unsigned +``` + + +测试方法: + +1. 将 test_export_A.c 和对应的 Makefile 拷贝到 module_A 文件夹中 + +2. 将 test_export_B.c 和对应的 Makefile 拷贝到 module_B 文件夹中 + +3. 编译 module_A 中的 test_export_A.c + +4. **将编译 module_A 后生成的 Module.symvers 拷贝到 module_B 文件夹中(重点,或者在makefile中指名查找目录)** + +5. 编译 module_B 中的 test_export_B.c + +6. 先安装 模块A,再安装模块B + +7. dmesg 查看log + +8. 用 rmmod 卸载模块B 和 模块A (注意卸载顺序,先卸载B再卸载A) +``` + +`Linux/Linux开发/Linux驱动调试.md`: + +```md +# Linux驱动调试 + +首先需要编译调试环境 + + +## 编译调试版本的驱动 + +### 将想要调试的驱动加载到内存,查看内存中模块的地址: + +``` +[root@localhost dev]# cat /proc/modules | grep inline +input_event_inlinehook 2119 0 - Live 0xffffffffa058a000 +[root@localhost dev]# +``` + +### 查看驱动文件的区块信息 + +``` +[root@localhost Documents]# objdump -h input_event_inlinehook.ko + +input_event_inlinehook.ko: file format elf64-x86-64 + +Sections: +Idx Name Size VMA LMA File off Algn + 0 .note.gnu.build-id 00000024 0000000000000000 0000000000000000 00000040 2**2 + CONTENTS, ALLOC, LOAD, READONLY, DATA + 1 .text 00000174 0000000000000000 0000000000000000 00000070 2**4 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 2 .exit.text 00000031 0000000000000000 0000000000000000 000001e4 2**0 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 3 .init.text 00000057 0000000000000000 0000000000000000 00000215 2**0 + CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE + 4 .rodata.str1.1 00000071 0000000000000000 0000000000000000 0000026c 2**0 + CONTENTS, ALLOC, LOAD, READONLY, DATA + 5 .modinfo 000000a1 0000000000000000 0000000000000000 000002e0 2**5 + CONTENTS, ALLOC, LOAD, READONLY, DATA +``` + +调试需要的是`.text`区块 + +### 添加调试符号 + +0xffffffffa058a000 + 0x00000070 = 0xffffffffa058a070 + +``` +(gdb) add-symbol-file /home/superman/Documents/input_event_inlinehook.ko 0xffffffffa058a070 +add symbol table from file "/home/superman/Documents/input_event_inlinehook.ko" at + .text_addr = 0xffffffffa058a070 +(y or n) y +Reading symbols from /home/superman/Documents/input_event_inlinehook.ko...done. +``` + +之后就可以下断点了: + +``` +(gdb) b install_hook + +Breakpoint 1 at 0xffffffffa058a140: file /home/superman/Documents/input_event_inlinehook.c, line 104. +``` + +遇到问题: + +调试机现在可下断点,但目标机运行时,在断点处不会断下来。 + +--- + +以上方法是断不下来的,现使用下面的方式 + +``` +// 被调试机 +[root@localhost Documents]# cat /sys/module/input_event_inlinehook/sections/.text +0xffffffffa057e000 + +// 调试机 +(gdb) add-symbol-file /home/superman/Documents/input_event_inlinehook.ko 0xffffffffa057e000 -s .bss 0xffffffffa057e5b0 -s .data 0xffffffffa057e2a0 +add symbol table from file "/home/superman/Documents/input_event_inlinehook.ko" at + .text_addr = 0xffffffffa057e000 + .bss_addr = 0xffffffffa057e5b0 + .data_addr = 0xffffffffa057e2a0 +(y or n) y +Reading symbols from /home/superman/Documents/input_event_inlinehook.ko...done. +``` + + + +**注意** + +在gcc和gdb版本不匹配的情况下,可能会出现`Single stepping until exit... which has no line number information`的情况,gcc(4.4)、gdb(7.8)测试可用。 +``` + +`Linux/Linux开发/Makefile.md`: + +```md +## 规则 + +Makefile是一些规则的集合 + +规则如下: +``` +<目标> : <前提依赖...> + <命令> +``` + +`<目标>`是必须的,`<前提依赖...>`和`<命令>`都是可选的,但是至少有其中的一个,不能都省略。 + + +在默认情况下,不指定目标的情况下,会自动执行第一个目标。 + +如: + +``` +tutorial: + @# todo: have this actually run some kind of tutorial wizard? + @echo "Please read the 'Makefile' file to go through this tutorial" +``` + +在不指定的情况下,`make`和`make tutorial`功能一样。 + + +## 抑制输出 + +在Unix环境下,一般保持一种风格,即“成功则沉默”,如果没有错误,就不会输出信息。 + +在上面的例子中,`@`符号用来抑制输出,如果没有此符号的话,会有如下输出: + +``` +# todo: have this actually run some kind of tutorial wizard? +echo "Please read the 'Makefile' file to go through this tutorial" +Please read the 'Makefile' file to go through this tutorial +``` + +## 变量 + +在makefile文件中,每一行命令都作为一个单独的shell执行,所以,当设置了一个变量后,它不会在下一行生效。如下: + +``` +var-lost: + export foo=bar + echo "foo = [$$foo]" +``` +结果如下: + +``` +root@ubuntu:/home/superman/LearnLinux# make var-lost +export foo=bar +echo "foo = [$foo]" +foo = [] +``` + +怎样使变量生效呢: + +方式一: + +``` +var-kept: + export foo=bar; \ + echo "foo=[$$foo]" +``` + +方式二: + +``` +var-kept: + export foo=bar;echo "foo=[$$foo]" +``` + +方式三: + +``` +.ONESHELL: +var-kept: + export foo=bar + echo "foo=[$$foo]" +``` + +## 依赖 + +现在我们开始做一个依赖其他事物的事情 + +``` +reslut.txt : source.txt + @echo "building result.txt from source.txt" + cp source.txt result.txt +``` + +当我们直接运行`make result.txt`时,我们会收到一个错误: + +``` +root@ubuntu:/home/superman/LearnLinux# make result.txt +make: *** No rule to make target 'source.txt', needed by 'result.txt'. Stop. +``` + +这是因为我们告知了怎样创建`result.txt`,但是没有告知怎么获取`source.txt`,并且此文件也没有在当前目录下。所以在上面的命令后添加如下: + +``` +source.txt: + @echo "building source.txt" + echo "this is the source" > source.txt +``` + +现在我们可以运行`touch source.txt`,或者编辑文件,`make result.txt`可以重新生成result.txt。 + +## 多个文件 + +当我们一个工程有100个.c 文件时,并且每个.c文件都需要转换成.o文件,最后链接所有的.o文件到一个二进制文件。 + +如果按照上面的依赖规则也可以完成,但是这将是一个很大并且很无聊的工作。 + +我们可以通用的规则来处理匹配模式的文件,并声明要将其转换成另一种模式的对应文件。 + +在规则集中,我们可以使用特殊的语法来引用输入文件和输出文件,下面是一些特殊变量: + +- `$@` + 代表这个规则要创建的文件,即目标文件。@像字母a,代表参数(arguments),当输入`make foo`时,`foo`就是这个参数。 + +- `$<` + 代表输入的文件(即列表中的一个依赖文件) + +- `$^` + 代表所有的输入文件,上面的那个是单一文件,这个是文件的一个集合 + +- `$?` + 代表所有比目标程序更新的文件,为什么要这么做呢?我们可以通过这个找出哪些文件做出了改变 + +- `$*` + 代表匹配符 % 匹配的部分,比如匹配 f1.txt中的f1, `$*`就表示f1 + +- `$(@D)`和`$(@F)` + `$(@D)` 和 `$(@F)` 分别指向 `$@` 的目录名和文件名。比如,`$@`是 src/input.c,那么`$(@D)` 的值为 src ,`$(@F)` 的值为 input.c。 + +- `$( $@ +``` + +不必为每个文件去运行make,我们可以定义一个依赖所有文件的`PHONY`目标,并且没有任何规则。 + +```shell +source : $(srcfiles) +``` + +## .PHONY +.PHONY: clean + - means the word "clean" doesn't represent a file name in this Makefile; + - means the Makefile has nothing to do with a file called "clean" + in the same directory. + +就是表示 如果在当前目录下有和make 目标相同的文件名或文件夹时,不理会这些情况,仍然执行Makefile文件中的指令。 + + + +例: + +```shell +make clean +``` + +但是,如果当前目录中,正好有一个文件叫做clean,那么这个命令不会执行。因为Make发现clean文件已经存在,就认为没有必要重新构建了,就不会执行指定的rm命令。 + +为了避免这种情况,可以明确声明clean是"伪目标",写法如下。 + +```shell +.PHONY: clean +clean: + rm *.o temp +``` + +声明clean是"伪目标"之后,make就不会去检查是否存在一个叫做clean的文件,而是每次运行都执行对应的命令。像.PHONY这样的内置目标名还有不少,可以查看[手册](http://www.gnu.org/software/make/manual/html_node/Special-Targets.html#Special-Targets)。 + + + +参考:[Make 命令教程 - 阮一峰的网络日志 (ruanyifeng.com)](http://www.ruanyifeng.com/blog/2015/02/make.html) + + +``` + +`Linux/Linux开发/code/fifo/CMakeLists.txt`: + +```txt +cmake_minimum_required(VERSION 3.7) +project(fifo) + +set(CMAKE_CXX_STANDARD 11) + +add_executable(fifo_w fifo_write.cpp) + +add_executable(fifo_r fifo_read.cpp) +``` + +`Linux/Linux开发/code/fifo/fifo_read.cpp`: + +```cpp +#include +#include +#include +#include +#include +#include + +#define FIFO_NAME "/var/tmp/fifo_test" + + +int main() +{ + int pipefd = 0; + + pipefd = open(FIFO_NAME, O_RDONLY); + + printf("read open success\n"); + + char buf[BUFSIZ] = {0}; + + while (1) { + int read_bytes = read(pipefd, buf, BUFSIZ); + + printf("read bytes = %d\n", read_bytes); + + if (read_bytes == 0) { + close(pipefd); + printf("write peer close fd\n"); + return 0; + } + + write(STDOUT_FILENO, buf, read_bytes); + } + + close(pipefd); + + return 0; +} +``` + +`Linux/Linux开发/code/fifo/fifo_write.cpp`: + +```cpp +#include +#include +#include +#include +#include +#include + +#define FIFO_NAME "/var/tmp/fifo_test" + +int main() +{ + int ret = 0; + int pipefd = 0; + // 判断fifo文件是否已存在 + if (access(FIFO_NAME, F_OK) == -1) { + // 不存在则创建 + ret = mkfifo(FIFO_NAME, 0666); + + if (ret == -1) { + perror("mkfifo error"); + return -1; + } + } + + + // 以写端打开 + pipefd = open(FIFO_NAME, O_WRONLY); + + printf("write open success\n"); + + if (pipefd == -1) { + perror("open error"); + return -1; + } + + char buf[BUFSIZ] = {0}; + + while (1) { + // 从控制台获取输入 + int read_bytes = read(STDIN_FILENO, buf, BUFSIZ); + + if( read_bytes == -1 ) { + perror("read error"); + close(pipefd); + return -1; + } + + // 写到管道 + int write_bytes = write(pipefd, buf, read_bytes); + } + + close(pipefd); + + + + return 0; +} + +``` + +`Linux/Linux开发/kernel/Linux内核反汇编.md`: + +```md +# Linux内核之vmlinuz反汇编 + + + +## 1.安装kernel-devel包 + + + +1. Ubuntu中安装kernel-devel + +  kernel-devel is a RedHat and derivatives package. In Ubuntu you may need to install the linux-kernel-headers and kernel-package. Try the following command in Terminal: + +```shell +sudo apt-get install linux-kernel-headers kernel-package +``` + + + +2. RedHat系 + +```shell +sudo yum install kernel-devel +``` + + + +## 2.提取vmlinux + +- vmlinux是一个包括Linux kernel的静态链接的可运行文件。 +- vmlinuz是vmlinux经过gzip和objcopy制作出来的压缩文件。 + +1. RedHat系 + +```shell +/usr/src/kernels/$(uname -r)/scripts/extract-vmlinux /boot/vmlinuz-$(uname -r) > vmlinux +``` + +2. debian系 + +```shell +/usr/src/$(uname -r)/scripts/extract-vmlinux /boot/vmlinuz-$(uname -r) > vmlinux +``` + + + +## 3.反汇编vmlinux + +```shell +objdump -D vmlinux > vmlinux.out +``` + + + +## 4.查看vmlinux里的函数 + +这里以函数tcp4_proc_init为例。/proc/kallsyms存储了所有的内核符号表,/boot/System.map则存储了静态的内核符号表。有关System.map,请阅读[这里](https://rlworkman.net/system.map/)。 + +```shell +root@superman-PC:/usr/src/linux-headers-5.4.50-amd64-desktop# grep input_event /proc/kallsyms +ffffffffa124d440 T input_event +ffffffffa124d720 T input_event_from_user +ffffffffa124d7e0 T input_event_to_user +ffffffffa12ea130 t hidinput_input_event +ffffffffa12ef2f5 t hidinput_input_event.cold.24 +ffffffffa1e922c0 r __ksymtab_input_event +ffffffffa1ea345c r __ksymtab_input_event_from_user +ffffffffa1ea3468 r __ksymtab_input_event_to_user +ffffffffa1ed31e1 r __kstrtab_input_event +ffffffffa1ed3207 r __kstrtab_input_event_to_user +ffffffffa1ed321b r __kstrtab_input_event_from_user +ffffffffc0636000 t snd_rawmidi_input_event_work [snd_rawmidi] +``` + + + +```shell +root@superman-PC:/usr/src/linux-headers-5.4.50-amd64-desktop# grep input_event /boot/System.map-5.4.50-amd64-desktop +ffffffff8184d440 T input_event +ffffffff8184d720 T input_event_from_user +ffffffff8184d7e0 T input_event_to_user +ffffffff818ea130 t hidinput_input_event +ffffffff818ef2f5 t hidinput_input_event.cold.24 +ffffffff824922c0 r __ksymtab_input_event +ffffffff824a345c r __ksymtab_input_event_from_user +ffffffff824a3468 r __ksymtab_input_event_to_user +ffffffff824d31e1 r __kstrtab_input_event +ffffffff824d3207 r __kstrtab_input_event_to_user +ffffffff824d321b r __kstrtab_input_event_from_user +``` + + + +在vim中查找`ffffffff8184d440`,找到函数汇编 + +![image-20210305114603227](images/Linux内核反汇编/image-20210305114603227.png) +``` + +`Linux/Linux开发/kernel/Linux文件系统.md`: + +```md +# Linux文件系统 + + + +## 目录 + +- /bin + + 基本命令 + +- sbin + + 系统命令 + +- /dev + + 设备文件存储目录,应用程序通过对这些文件的读写和控制以访问实际的设备 + +- /etc + + 系统配置文件的所在地,一些服务器的配置文件也在这里 + +- /lib + + 系统库文件存放目录 + +- /mnt + + 一般是用于存放挂载储存设备的挂载目录 + +- /opt + + 有此软件包会被安装在这里 + +- /proc + + 操作系统运行时,进程及内核信息(比如CPU、硬盘分区、内存信息等)存放在这里。proc目录为伪文件系统proc的挂载目录,proc并不是真正的文件系统,它存在于内核之中。 + +- /tmp + + 用户运行程序的时候,有时会产生临时文件,/tmp用来存放临时文件 + +- /usr + + 系统存放程序的目录 + +- /var + + /var/log目录经常存放系统目录 + +- /sys + + 2.6内核之后的内核所支持的sysfs文件系统被映射在此目录上。Linux设备驱动模型中的总线、驱动和设备都可以在sysfs文件系统中找到对应的节点。当内核检测到在系统中出现了新设备后,内核会在sysfs文件系统中为该新设备生成一项新的记录。 + + + +## Linux文件系统与设备驱动 + + + +应用程序和VFS之间的接口是系统调用。 + + + +VFS与文件系统以及设备文件之间的接口是`file_operations`结构体成员函数。这个结构体包含对文件进行打开、关闭、读写、控制的一系统函数。 + + + +通过文件系统来访问块设备,`file_operations`的实现则位于文件系统内,文件系统会把针对文件的读写转换为针对块设备原始扇区的读写。 + + + + + +## VFS对象及其数据结构 + +VFS中有四个主要的对象类型,它们分别是: + +- 超级块对象:它代表一个具体的已安装文件系统 +- 索引节点对象:它代表一个具体文件 +- 目录项对象:它代表一个目录项,是路径组成的一部分 +- 文件对象:它代表由进程打开的文件 + + + +## get_sb分析 + + + +>The get_sb() method has the following arguments: +> struct file_system_type *fs_type: describes the filesystem, partly initialized by the specific filesystem code +> int flags: mount flags +> const char *dev_name: the device name we are mounting. +> void *data: arbitrary mount options, usually comes as an ASCII string (see "Mount Options" section) +> struct vfsmount *mnt: a vfs-internal representation of a mount point + + + +共有5个参数: + +- fs_type +- flags 挂载标志 +- dev_name 将要挂载的设备名 +- data 挂载选项 +- mnt 一个挂载点的vfs内部表示,一个系统实例 + + + +get_sb()方法必须确定dev_name和fs_type中指定的块设备是否包含该方法支持的文件系统。如果成功打开命名的块设备,它将为该块设备所包含的文件系统初始化一个struct super_block描述符。失败时返回错误。 + +get_sb()方法填充的超级块结构中最有趣的成员是“ s_op”字段。这是指向“ struct super_operations”的指针,该指针描述了文件系统实现的下一级。 + + + +## get_sb_nodev 函数分析 + + + +通常,文件系统使用一个通用的get_sb()实现,并提供一个fill_super()方法。 + + + +- get_sb_bdev:安装在块设备上的文件系统 + +- get_sb_nodev:安装不受设备支持的文件系统 + +- get_sb_single:安装在以下设备之间共享实例的文件系统 + + + +```c +int get_sb_nodev(struct file_system_type *fs_type, + int flags, void *data, + int (*fill_super)(struct super_block *, void *, int), + struct vfsmount *mnt) +{ + int error; + struct super_block *s = sget(fs_type, NULL, set_anon_super, NULL); + + if (IS_ERR(s)) + return PTR_ERR(s); + + s->s_flags = flags; + + error = fill_super(s, data, flags & MS_SILENT ? 1 : 0); + if (error) { + deactivate_locked_super(s); + return error; + } + s->s_flags |= MS_ACTIVE; + simple_set_mnt(mnt, s); + return 0; +} +``` + + + +fill_super 是由开发者提供的sb填充的函数指针。 + + + +fill_super函数主要填充的sb结构体中的字段有: + +- s_fs_info 文件系统特殊信息,可以存放一些自己构造的数据 +- s_op 超级块操作方法 +- s_root 目录挂载点,`struct dentry *`类型,使用`d_alloc`函数分配 +- s_root->d_op 目录项操作指针 +- s_root->d_sb 文件所属超级块,就是自己 +- s_root->d_parent 父目录的目录项对象 +- s_root->d_fsdata 分配内存,存放文件系统的特有数据 + + + + + + + + +``` + +`Linux/Linux开发/kernel/Linux文件系统/dentry结构体.md`: + +```md +# 目录项对象 + + + +**VFS把目录当作文件看待**,所以在路径`/bin/vi`中,bin和vi都属于文件: + +bin是特殊的目录文件 + +vi是普通文件 + +所以,路径中的每个组成部分都由一个索引节点对象表示。 + + + +既然这样,为什么还要有目录项这个东西呢? + + + +虽然它们可以统一由索引节点表示,但是VFS经常需要执行目录相关的操作,比如路径名查找等。路径名查找需要解析路径中的每一个组成部分,不但要确保它有效,而且还需要再进一步寻找路径中的下一部分。 + +为了方便查找,VFS引入了目录项。每个`dentry`代表路径中的一个特定部分。对于`/bin/vi`来说,`/`、`bin`、`vi`都属于目录项对象。 + + + +所以,在路径中(包括普通文件),每一部分都是目录项对象。 + + + +结构体如下: + +```c +struct dentry { + atomic_t d_count; + unsigned int d_flags; /* protected by d_lock */ + spinlock_t d_lock; /* per dentry lock */ + int d_mounted; + struct inode *d_inode; /* Where the name belongs to - NULL is + * negative */ + /* + * The next three fields are touched by __d_lookup. Place them here + * so they all fit in a cache line. + */ + struct hlist_node d_hash; /* lookup hash list */ + struct dentry *d_parent; /* parent directory */ + struct qstr d_name; + + struct list_head d_lru; /* LRU list */ + /* + * d_child and d_rcu can share memory + */ + union { + struct list_head d_child; /* child of parent list */ + struct rcu_head d_rcu; + } d_u; + struct list_head d_subdirs; /* our children */ + struct list_head d_alias; /* inode alias list */ + unsigned long d_time; /* used by d_revalidate */ + const struct dentry_operations *d_op; + struct super_block *d_sb; /* The root of the dentry tree */ + void *d_fsdata; /* fs-specific data */ + + unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* small names */ +}; + +#ifdef __LITTLE_ENDIAN +#define HASH_LEN_DECLARE u32 hash; u32 len; +#else +#define HASH_LEN_DECLARE u32 len; u32 hash; +#endif + +struct qstr { + union { + struct { + HASH_LEN_DECLARE; + }; + u64 hash_len; + }; + const unsigned char *name; +}; +``` + + + +- d_inode:相关联的索引节点 + +- d_parent:每个dentry的父目录是唯一的,所以dentry 有成员变量d_parent + +- d_name:文件名 + + qstr是quick string的缩写,注意,qstr中的name只存放路径的最后一个分量,即basename,/usr/bin/vim只会存放vim这个名字。当然,如果路径名比较短,就存放在`d_iname`中。注意此结构体中有hash,还有一个len变量隐藏在struct HASH_LEN_DECLARE中。 + +- d_subdirs:dentry也会有子目录对应的dentry,所以提供了d_subdirs,所有子目录对应的dentry都会挂在该链表上。 + +- d_child:dentry靠什么挂在其父dentry的以d_subdirs为头部的链表上?靠的是d_child成员比变量。 + +- d_hash:根据上面的结构体,已经可以查找某个目录是否在内存的dentry中,但是用链表查找太慢了,所以内核提供了hash表,d_hash会放置到hash表某个头节点所在的链表。 + + 但是计算hash值并不是简单的根据目录的basename来hash,否则会有大量的冲突,比如home下每一个用户的家目录中都会有Pictures,Video,Project等目录,名字重复的概率太高,因此,计算hash的时候,将父dentry的地址也放入了hash计算当中,影响最后的结果,这大大降低了发生碰撞的机会。 + + 也就是说一个dentry的hash值,取决于两个值:父dentry的地址和该dentry路径的basename。 + + + + ```c + static inline struct hlist_bl_head *d_hash(const struct dentry *parent, + unsigned int hash) + { + hash += (unsigned long) parent / L1_CACHE_BYTES; + return dentry_hashtable + hash_32(hash, d_hash_shift); + } + ``` + + 注意,如果一个目录book,但是每一次都要计算该basename的hash值,就会每次查找不得不计算一次book的hash,那效率就低了,因此, qstr结构体中有一个字段hash,一次算好,再也不算了。此处是稍微牺牲空间效率来提升时间效率,用空间来换时间,可以看出Linux将性能压榨到了极致,能提升性能的地方,绝不放过。 + + + +当然了,一开始可能某个目录对应的dentry根本就不在内存中,所以会有d_lookup函数,以父dentry和qstr类型的name为依据,来查找内存中是否已经有了对应的dentry,当然,如果没有,就需要分配一个dentry,这是d_alloc函数负责分配dentry结构体,初始化相应的变量,建立与父dentry的关系。 + + + +## **当我们读写文件时底层的工作原理是什么?** + +![img](images/dentry结构体/v2-b5d3e0b7b1f2a1aecb89ed0c93ac907c_720w.jpg) + +访问一个文件的时候,先进入目录,目录中有相应的目录项,目录项中有对应的文件名和inode号,通过里面的指针指向相应的数据块,这样就访问到文件的内容了。目录这种“特殊的文件”,可以简单地理解为是一张表,这张表里面存放了属于该目录的文件的文件名,以及所匹配的inode编号,它本身的数据也放在数据区中;读写一个文件时就这样来回的从inode和数据区之间切换。可以把文件比作一本书,inode相当于书的目录,数据区相当于书的内容,读书时得先查目录,这本书呢又放在读书馆的书架上,书架可以理解为是目录,看书前先查书架子的索引。 + + + +一个被使用的目录项对应一个有效的索引节点(即d_inode撒向相应的索引节点)并且表明该对象存在一个或多个使用者(即d_count为正值)。 + +当d_count为0时,表明这个目录项未被使用,但该目录项仍指向一个有效的索引节点,此时应保留在缓存中以便需要时再使用。不会被过早地被撤销,所以以后再需要它时,不必重新创建,与未缓存的目录项对比,这样全路径查找更迅速。 + +当一个目录项没有对应有效的索引节点时(因为索引节点已经被删除了,或路径不再正确),这就是一个无效目录项,但这个目录项仍然保留,以便快速解析以后的路径查询。但open这样的系统调用会直接返回错误,不常用。 + + + + +``` + +`Linux/Linux开发/kernel/Linux文件系统/path结构体.md`: + +```md +# path + +```c +struct path { + struct vfsmount *mnt; + struct dentry *dentry; +}; +``` + + +``` + +`Linux/Linux开发/kernel/Linux文件系统/创建文件.md`: + +```md +# vfs_create + + + +源码如下: + +```c +int vfs_create(struct inode *dir, struct dentry *dentry, int mode, + struct nameidata *nd) +{ + int error = may_create(dir, dentry); + + if (error) + return error; + + if (!dir->i_op->create) + return -EACCES; /* shouldn't it be ENOSYS? */ + mode &= S_IALLUGO; + mode |= S_IFREG; + error = security_inode_create(dir, dentry, mode); + if (error) + return error; + vfs_dq_init(dir); + error = dir->i_op->create(dir, dentry, mode, nd); + if (!error) + fsnotify_create(dir, dentry); + return error; +} +``` + + + +比如要在/home/superman/下创建test.txt文件 + + + +## 参数 + +- dir superman目录项所对应的索引节点 +- dentry test.txt目录项 +- mode 创建时的属性 +- nd 文件名及其他相关数据 + + +``` + +`Linux/Linux开发/kernel/Linux文件系统/索引节点对象.md`: + +```md +# 索引节点对象 inode结构体 + +索引节点对象包含了内核在操作**文件**或**目录**时需要的全部信息。 + + + +Unix风格的文件系统,**数据与控制信息是分开存放的**。 + +原因: + +> inode又要扯到存储了,我们知道数据是保存在磁盘中的,磁盘上最小存储数据的是扇区,每个扇区一般都是可以存放512字节的数据。 + +> 那么如果数据大于512字节时候,磁盘需要不停地移动磁头来查找数据,我们知道一般地文件很容易超过512字节那么如果把多个扇区合并为一个块,那么磁盘就可以提高效率了,。那么磁头一次读取多个扇区就为一个块“block”(linux上称为块,Windows上称为簇)。 + +> 一个块多为4KB,因为块是文件系统层面上的概念,所以块也可以在格式化时候自行定义。 + +> 文件数据都储存在"块"中,那么很显然,我们还必须找到一个地方储存文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做inode,中文译名为"索引节点"。 + + + +索引节点对象必须在内存中创建。 + + + +## inode的内容 + +inode包含文件的元信息,具体来说有以下内容: + +```shell +  * 文件的字节数 + +  * 文件拥有者的User ID + +  * 文件的Group ID + +  * 文件的读、写、执行权限 + +  * 文件的时间戳,共有三个:ctime指inode上一次变动的时间,mtime指文件内容上一次变动的时间,atime指文件上一次打开的时间。 + +  * 链接数,即有多少文件名指向这个inode + +  * 文件数据block的位置 +``` + + + +## 结构体 + + + +结构如下: + +```c +struct inode { + struct hlist_node i_hash; // 散列表 + struct list_head i_list; /* backing dev IO list 索引节点链表 */ + struct list_head i_sb_list; // 超级块链表 + struct list_head i_dentry; // 目录项链表 + unsigned long i_ino; // 节点号 + atomic_t i_count; // 引用计数 + unsigned int i_nlink; // 硬链接数 + uid_t i_uid; // 使用者id + gid_t i_gid; // 使用组id + dev_t i_rdev; // 实际设备标识符 + u64 i_version; // 版本号 + loff_t i_size; // 以字节为单位的文件大小 +#ifdef __NEED_I_SIZE_ORDERED + seqcount_t i_size_seqcount; +#endif + struct timespec i_atime; // 最后访问时间 + struct timespec i_mtime; // 最后修改时间 + struct timespec i_ctime; // 最后改变时间 + blkcnt_t i_blocks; // 文件块数 + unsigned int i_blkbits; // 以位为单位的块大小 + unsigned short i_bytes; // 使用的字节数 + umode_t i_mode; // 访问权限 + spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */ + struct mutex i_mutex; + struct rw_semaphore i_alloc_sem; + const struct inode_operations *i_op; // 索引节点的操作表 + const struct file_operations *i_fop; /* former ->i_op->default_file_ops 缺省的索引节点操作*/ + struct super_block *i_sb; // 相关的超级块 + struct file_lock *i_flock; // 文件锁链表 + struct address_space *i_mapping; //相关的地址映射 + struct address_space i_data; // 设备地址映射 +#ifdef CONFIG_QUOTA + struct dquot *i_dquot[MAXQUOTAS]; // 索引节点的磁盘限额 +#endif + struct list_head i_devices; // 块设备链表 + union { + struct pipe_inode_info *i_pipe; // 管道信息 + struct block_device *i_bdev; // 块设备驱动 + struct cdev *i_cdev; // 字符设备驱动 + }; + + __u32 i_generation; + +#ifdef CONFIG_FSNOTIFY + __u32 i_fsnotify_mask; /* all events this inode cares about */ + struct hlist_head i_fsnotify_mark_entries; /* fsnotify mark entries */ +#endif + +#ifdef CONFIG_INOTIFY + struct list_head inotify_watches; /* watches on this inode */ + struct mutex inotify_mutex; /* protects the watches list */ +#endif + + unsigned long i_state; // 状态标志 + unsigned long dirtied_when; /* jiffies of first dirtying 第一次弄脏数据的时间 */ + + unsigned int i_flags; // 文件系统标志 + + atomic_t i_writecount; // 写者计数 +#ifdef CONFIG_SECURITY + void *i_security; // 安全模块 +#endif +#ifdef CONFIG_FS_POSIX_ACL + struct posix_acl *i_acl; + struct posix_acl *i_default_acl; +#endif + void *i_private; /* fs or device private pointer fs私有指针*/ +}; +``` + + + +- i_ino:节点号,Unix/Linux系统内部不使用文件名,而使用inode号码来识别文件。对于系统来说,文件名只是inode号码便于识别的别称或者绰号。 + + 表面上,用户通过文件名,打开文件。实际上,系统内部这个过程分成三步:首先,系统找到这个文件名对应的inode号码;其次,通过inode号码,获取inode信息;最后,根据inode信息,找到文件数据所在的block,读出数据。 + + ```sh el l + ls -i example.txt + ``` + + ![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/bg2011120405.png) + + + +​ 目录(directory)也是一种文件。打开目录,实际上就是打开目录文件。 + + + +### inode号码的特殊作用 + +由于inode号码与文件名分离,这种机制导致了一些Unix/Linux系统特有的现象。 + +1. 有时,文件名包含特殊字符,无法正常删除。这时,直接删除inode节点,就能起到删除文件的作用。 + + ```sh el l + find ./* -inum 节点号 -delete + ``` + +2. 移动文件或重命名文件,只是改变文件名,不影响inode号码。 + +3. 打开一个文件以后,系统就以inode号码来识别这个文件,不再考虑文件名。因此,通常来说,系统无法从inode号码得知文件名。 + +​ + +第3点使得软件更新变得简单,可以在不关闭软件的情况下进行更新,不需要重启。因为系统通过inode号码,识别运行中的文件,不通过文件名。更新的时候,新版文件以同样的文件名,生成一个新的inode,不会影响到运行中的文件。等到下一次运行这个软件的时候,文件名就自动指向新版文件,旧版文件的inode则被回收。 + + + +## 其他 + + + +一个索引节点代表文件系统中(**索引节点仅当文件被访问时,才在内存中创建** + +)的一个文件,它也可以是设备或管道这样的特殊文件。 + + + +inode,和文件是一对一关系。 + + + +现代的操作系统,都会设计文件缓存。因为文件位于慢速的块设备上,如果操作系统不设计缓存,每一次对文件的读写都要走到块设备,速度是不能容忍的。对于Linux而言,实现了页高速缓存。我们有这个感觉如果一次读某个文件慢的话,紧接着读这个文件第二次,速度会有明显的提升。原因是Linux已经将文件的部分内容或者全部内容缓存到了页高速缓存。 + + + +![image-20200814151919593](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200814151919593.png) +``` + +`Linux/Linux开发/kernel/Linux文件系统/超级块对象.md`: + +```md +# 超级块对象 + + + + +``` + +`Linux/Linux开发/kernel/Linux输入子系统/Linux输入子系统.md`: + +```md +# Linux输入子系统框架 + +## 输入子系统概述 + +Linux内核为了能够处理各种不同类型的输入设备,比如 触摸屏 ,鼠标 , 键盘 , 操纵杆 ,设计并实现了为驱动层程序的实现提供统一接口函数;为上层应用提供试图统一的抽象层 , 即是Linux 输入子系统 。 + +![img](images/Linux输入子系统/14336242-c446ab163256b4e4.png) + +从上图输入子系统的框架图,可以看出,输入子系统由Input driver(驱动层)、Input core(输入子系统核心)、Event handler(事件处理层)三部分组成。一个输入事件,如鼠标移动、键盘按下等通过Input driver -> Input core -> Event handler -> userspace的顺序到达用户空间的应用程序。 + + + +- Input driver :主要实现对硬件设备的读写访问,中断设置,并把硬件产生的事件转换为核心层定义的规范提交给事件处理层。 + +- Input core :承上启下。为设备驱动层提供了规范和接口;通知事件处理层对事件进行处理; + +- Event handler :提供用户编程的接口(设备节点),并处理驱动层提交的数据处理。 + + + +## 输入子系统框架分析 + +输入子系统是所有I/O设备驱动的中间层,为上层提供了一个统一的界面。例如,在终端系统中,我们不需要去管有多少个键盘,多少个鼠标。它只要从输入子系统中去取对应的事件(按键,鼠标移位等)就可以了。 + +上面,我们从功能级别,描述了输入子系统每一层,做了些什么。接下来,我们将从代码级别的角度出发,分析系统核心层、事件处理层、设备驱动层。 + +### 1.系统核心层(Input core) + +- 申请主设备号; +- 提供input_register_device跟input_register_handler函数分别用于注册device跟handler; +- 提供input_register_handle函数用于注册一个事件处理,代表一个成功配对的input_dev和input_handler; + +### 2.事件处理层(Event handler) + +- 不涉及硬件方面的具体操作,handler层是纯软件层,包含不同的解决方案,如键盘,鼠标,游戏手柄等; +- 对于不同的解决方案,都包含一个名为input_handler的结构体,该结构体内含的主要成员如下: + +| **成员** | **功能** | +| ----------- | ------------------------------------------------------------ | +| .id_table | 一个存放该handler所支持的设备id的表(其实内部存放的是EV_xxx事件,用于判断device是否支持该事件) | +| .fops | 该handler的file_operation | +| .connect | 连接该handler跟所支持device的函数 | +| .disconnect | 断开该连接 | +| .event | 事件处理函数,让device调用 | +| h_list | 是一个链表,该链表保存着该handler到所支持的所有device的中间站:handle结构体的指针 | + +### 3.设备驱动层(Input driver) + +- device是纯硬件操作层,包含不同的硬件接口处理,如gpio等 +- 对于每种不同的具体硬件操作,都对应着不同的input_dev结构体 +- 该结构体内部也包含着一个h_list,指向handle + +### 4.两条链表一个结构 + +- 对于handler和device,分别用链表input_handler_list和input_device_list进行维护,当handler或者device增加或减少的时候,分别往这两链表增加或删除节点,这两条都是全局链表。 + +- input_handle 结构体代表一个成功配对的input_dev和input_handler。input_hande 没有一个全局的链表,它注册的时候将自己分别挂在了input_device_list和 input_handler_list的h_list上了;同时,input_handle的成员`.*dev`,关联到input_dev结构,`.*handler`关联到input_handler结构 。从此,建立好了三者的铁三角关系,通过任何一方,都可以找到彼此 + + + +![img](images/Linux输入子系统/14336242-0cc0ae43b7c25aef.png) + +总结一下,输入子系统作为一个模块存在;向上,为用户层提供调用接口;向下,为驱动层程序提供统一的注册接口。这样,就能够使输入设备的事件通过输入子系统发送给用户层应用程序,用户层应用程序也可以通过输入子系统通知驱动程序完成某项功能。(Linux中在用户空间将所有的设备都当初文件来处理,由于在一般的驱动程序中都有提供fops接口,以及在/dev下生成相应的设备文件nod,这些操作在输入子系统中由事件处理层完成) + + + +## 输入子系统分析 + +Input core 作为输入子系统的核心,我们以他为入口,进行分析。内核所有的输入子系统核心代码在`driver/input`下; +driver/input/input.c (核心层) +找到入口函数: + +```c +subsys_initcall(input_init); +``` + +`input_init`:分析: + +```c +static int __init input_init(void) +{ + int err; + + err = class_register(&input_class); //在/sys/class下创建逻辑(input)类 + if (err) { + pr_err("unable to register input_dev class\n"); + return err; + } + + err = input_proc_init();//在/proc下面建立相关的文件 + if (err) + goto fail1; + + /*申请一个字符设备,主设备号13*/ + err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0), + INPUT_MAX_CHAR_DEVICES, "input"); + if (err) { + pr_err("unable to register char major %d", INPUT_MAJOR); + goto fail2; + } + + return 0; + + fail2: input_proc_exit(); + fail1: class_unregister(&input_class); + return err; +} +``` + + + +在入口函数里面创建了一个input_class类,其实就在/sys/class下创建了一个目录input。 + +另外在/proc创建了入口项,这样就可以/proc目录看到input的信息. + +然后就注册设备,可以看出输入子系统的主设备号是13,在这里并没有生成设备文件.只是在/dev/目录下创建了input目录,以后所有注册进系统的输入设备文件都放在这个目录下。 + +到这里,输入子系统的核心初始化也就完成了,完成了?你可能会有这样的疑问: + +①为什么这里代码只创建逻辑(input)类,没有使用class_device_create()函数在类下面注册驱动设备? + +②为什么这里的代码只是申请了一个主设备号INPUT_MAJOR的字符设备,没有进行设备的注册? + +核心层作为一个中转层存在,不涉及具体硬件设备的注册,倒是更符合他存在的逻辑。那么猜测下,设备的注册到底会在Input driver 还是Event hanlder呢?往下分析... + +### **输入核心为驱动层提供统一的接口,涉及的结构和方法如下:** + +实现设备驱动核心工作是:向系统报告按键、触摸屏等输入事件(event,通过input_event结构描述),不再需要关心文件操作接口。驱动报告事件经过inputCore和Eventhandler到达用户空间。 + +#### input_dev结构 + +```c + struct input_dev { + const char *name; //提供给用户的输入设备的名称 + const char *phys; //提供给编程者的设备节点的名称 + const char *uniq; //指定唯一的ID号,就像MAC地址一样 + struct input_id id; //输入设备标识ID,用于和事件处理层进行匹配 + unsigned long evbit[NBITS(EV_MAX)]; // 记录设备支持的事件类型 + unsigned long keybit[NBITS(KEY_MAX)]; // 记录设备支持的按键类型 + unsigned long relbit[NBITS(REL_MAX)]; // 表示能产生哪些相对位移事件, x,y,滚轮 + unsigned long absbit[NBITS(ABS_MAX)]; // 表示能产生哪些绝对位移事件, x,y + unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; + unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; + unsigned long sndbit[BITS_TO_LONGS(SND_CNT)]; + unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; + unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; + unsigned int hint_events_per_packet; + unsigned int keycodemax; + unsigned int keycodesize; + void *keycode; + ... + } +``` + +| **功能** | **接口** | +| ------------------ | ------------------------------------------------------------ | +| 分配输入设备函数 | struct input_dev *input_allocate_device(void) | +| 注册输入设备函数 | int input_register_device(struct input_dev *dev) | +| 注销输入设备函数 | void input_unregister_device(struct input_dev *dev) | +| 事件支持(初始化) | set_bit()
告诉input输入子系统支持哪些事件,哪些按键,
例如:
set_bit(EV_KEY,button_dev.evbit)
(其中button_dev是struct input_dev类型)
struct input_dev中有两个成员为:
evbit: 事件类型(EV_RST,EV_REL,EV_MSC,EV_KEY,EV_ABS,EV_REP等)
keybit: 按键类型(当事件类型为EV_KEY时包括BTN_LEFT,BTN_0,BTN_1,BTN_MIDDLE等) | +| 报告事件 | void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) 在发生输入事件时,向子系统报告事件。
参数说明:
input_dev *dev :要上报哪个input_dev驱动设备的事件;
type : 要上报哪类事件, 比如按键事件,则填入: EV_KEY;
code: 对应的事件里支持的哪个变量,比如按下按键L则填入: KEY_L;
value:对应的变量里的数值,比如松开按键则填入1,松开按键则填入0; | +| 报告结束 | input_sync()同步用于告诉input core子系统报告结束 | + +所以,对于Input driver的工作主要还是分配、设置、注册一个结构体。`input_register_device()`用于注册一个输入设备。那么注册过程是怎样的呢?这是一个重点,在下面的代码中进行注释分析: + +```c +int input_register_device(struct input_dev *dev) +{ + struct input_devres *devres = NULL; + /* 输入事件的处理接口指针,用于和设备的事件类型进行匹配 */ + struct input_handler *handler; + unsigned int packet_size; + const char *path; + int error; + + if (dev->devres_managed) { + devres = devres_alloc(devm_input_device_unregister, + sizeof(struct input_devres), GFP_KERNEL); + if (!devres) + return -ENOMEM; + + devres->input = dev; + } + + /* 默认所有的输入设备都支持EV_SYN同步事件 */ + /* Every input device generates EV_SYN/SYN_REPORT events. */ + __set_bit(EV_SYN, dev->evbit); + + /* KEY_RESERVED is not supposed to be transmitted to userspace. */ + __clear_bit(KEY_RESERVED, dev->keybit); + + /* Make sure that bitmasks not mentioned in dev->evbit are clean. */ + input_cleanse_bitmasks(dev); + + packet_size = input_estimate_events_per_packet(dev); + if (dev->hint_events_per_packet < packet_size) + dev->hint_events_per_packet = packet_size; + + dev->max_vals = dev->hint_events_per_packet + 2; + dev->vals = kcalloc(dev->max_vals, sizeof(*dev->vals), GFP_KERNEL); + if (!dev->vals) { + error = -ENOMEM; + goto err_devres_free; + } + + /* + * If delay and period are pre-set by the driver, then autorepeating + * is handled by the driver itself and we don't do it in input.c. + */ + if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) { + dev->timer.data = (long) dev; + dev->timer.function = input_repeat_key; + dev->rep[REP_DELAY] = 250; + dev->rep[REP_PERIOD] = 33; + } + + /*没有定义设备的getkeycode函数,则使用默认的获取键值函数*/ + if (!dev->getkeycode) + dev->getkeycode = input_default_getkeycode; + + /*没有定义设备的setkeycode函数,则使用默认的设定键值函数*/ + if (!dev->setkeycode) + dev->setkeycode = input_default_setkeycode; + + /*添加设备*/ + error = device_add(&dev->dev); + if (error) + goto err_free_vals; + + /* 获取并打印设备的绝对路径名称 */ + path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL); + pr_info("%s as %s\n", + dev->name ? dev->name : "Unspecified device", + path ? path : "N/A"); + kfree(path); + + error = mutex_lock_interruptible(&input_mutex); + if (error) + goto err_device_del; + + /* `重要`:把设备挂到全局的input子系统设备链表input_dev_list上 */ + list_add_tail(&dev->node, &input_dev_list); + + /* 核心重点,input设备在增加到input_dev_list链表上之后,会查找 + * input_handler_list事件处理链表上的handler进行匹配,这里的匹配 + * 方式与设备模型的device和driver匹配过程很相似,所有的input devicel + * 都挂在input_dev_list上,所有类型的事件都挂在input_handler_list + * 上,进行“匹配相亲”*/ + list_for_each_entry(handler, &input_handler_list, node) + input_attach_handler(dev, handler);/*遍历input_handler_list,试图与每一个handler进行匹配*/ + + input_wakeup_procfs_readers(); + + mutex_unlock(&input_mutex); + + if (dev->devres_managed) { + dev_dbg(dev->dev.parent, "%s: registering %s with devres.\n", + __func__, dev_name(&dev->dev)); + devres_add(dev->dev.parent, devres); + } + return 0; + +err_device_del: + device_del(&dev->dev); +err_free_vals: + kfree(dev->vals); + dev->vals = NULL; +err_devres_free: + devres_free(devres); + return error; +} +``` + + + +上面的代码主要的功能有以下几个功能,也是设备驱动注册为输入设备委托内核做的事情: + +- 添加设备; +- 把输入设备挂到输入设备链表`input_dev_list`中; +- 遍历`input_handler_list`链表,查找并匹配输入设备对应的事件处理层,如果匹配上了,就调用`handler`的`connnect`函数进行连接。设备就是在此时注册的,下面分析handler就清晰了。 + (input_attach_handler放到分析handler时再做讲解,更容易理解。) + +### 输入核心为事件管理层提供主要接口: + +事件处理层文件主要是用来支持输入设备并与用户空间交互,这部分代码一般不需要我们自己去编写,因为Linux内核已经自带有一些事件处理器,可以支持大部分输入设备,比如Evdev.c、mousedev.c、joydev.c等。 + +| 功能 | 接口 | +| :----------------------- | :--------------------- | +| 注册一个事件处理器 | input_register_handler | +| 向内核注册一个handle结构 | input_register_handle | + +对于Event handler,就是根据事件注册一个handler,将handler挂到链表input_handler_list下,然后遍历input_dev_list链表,查找并匹配输入设备对应的事件处理层,如果匹配上了,就调用`connect`函数进行连接,并创建input_handle结构。 + +下面以Evdev为例,来分析事件处理层。 +vim drivers/input/evdev.c +同样找到入口函数: + +```c +module_init(evdev_init); +``` + +`evdev_init`分析: + +```c +static int __init evdev_init(void) +{ + return input_register_handler(&evdev_handler); +} +``` + +直接调用input_register_handler 注册一个input_handler结构体,这下回到了输入核心层提供的接口input_register_handler上,接着往下看: + +```c +int input_register_handler(struct input_handler *handler) +{ + struct input_dev *dev; + int error; + + error = mutex_lock_interruptible(&input_mutex); + if (error) + return error; + + INIT_LIST_HEAD(&handler->h_list); + + /* `重要`:把设备处理器挂到全局的input子系统设备链表input_handler_list上 */ + list_add_tail(&handler->node, &input_handler_list); + + /*遍历input_dev_list,试图与每一个input_dev进行匹配*/ + list_for_each_entry(dev, &input_dev_list, node) + input_attach_handler(dev, handler); + + input_wakeup_procfs_readers(); + + mutex_unlock(&input_mutex); + return 0; +} +``` + +这个input_register_handler的注册过程,你可以能看起来觉得挺熟悉;没错,这个注册过程和input_register_device极其相似;下面就重点分析匹配连接过程中,事件处理层到底做了些什么。 +`input_attach_handler`匹配过程如下: + +```c +static int input_attach_handler(struct input_dev *dev, struct input_handler *handler) +{ + const struct input_device_id *id; + int error; + + /* 利用handler->id_table和dev进行匹配*/ + id = input_match_device(handler, dev); + if (!id) + return -ENODEV; + /*匹配成功,则调用handler->connect函数进行连接*/ + error = handler->connect(handler, dev, id); + if (error && error != -ENODEV) + pr_err("failed to attach handler %s to device %s, error: %d\n", + handler->name, kobject_name(&dev->dev.kobj), error); + + return error; +} +``` + +对于connect函数,每种事件处理器的实现都有差异,但原理都相同。他主要注册input_handle结构,然后将input_device和input_handler进行关联。 + +以evdev的connect的evdev_connect函数,代码注释分析下这个过程: + +```c +static int evdev_connect(struct input_handler *handler, struct input_dev *dev, + const struct input_device_id *id) +{ + struct evdev *evdev; + int minor; + int dev_no; + int error; + + /*申请一个新的次设备号*/ + minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true); + + /* 这说明内核已经没办法再分配这种类型的设备了 */ + if (minor < 0) { + error = minor; + pr_err("failed to reserve new minor: %d\n", error); + return error; + } + + /* 开始给evdev事件层驱动分配空间了 */ + evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); + if (!evdev) { + error = -ENOMEM; + goto err_free_minor; + } + + /* 初始化client_list列表和evdev_wait队列 */ + INIT_LIST_HEAD(&evdev->client_list); + spin_lock_init(&evdev->client_lock); + mutex_init(&evdev->mutex); + init_waitqueue_head(&evdev->wait); + evdev->exist = true; + + dev_no = minor; + /* Normalize device number if it falls into legacy range */ + if (dev_no < EVDEV_MINOR_BASE + EVDEV_MINORS) + dev_no -= EVDEV_MINOR_BASE; + + /*设置设备节点名称,/dev/eventX 就是在此时设置*/ + dev_set_name(&evdev->dev, "event%d", dev_no); + + /* 初始化evdev结构体,其中handle为输入设备和事件处理的关联接口 */ + evdev->handle.dev = input_get_device(dev); + evdev->handle.name = dev_name(&evdev->dev); + evdev->handle.handler = handler; + evdev->handle.private = evdev; + + /*设置设备号,应用层就是通过设备号,找到该设备的*/ + evdev->dev.devt = MKDEV(INPUT_MAJOR, minor); + evdev->dev.class = &input_class; + evdev->dev.parent = &dev->dev; + evdev->dev.release = evdev_free; + device_initialize(&evdev->dev); + + /* input_dev设备驱动和handler事件处理层的关联,就在这时由handle完成 */ + error = input_register_handle(&evdev->handle); + if (error) + goto err_free_evdev; + + cdev_init(&evdev->cdev, &evdev_fops); + evdev->cdev.kobj.parent = &evdev->dev.kobj; + error = cdev_add(&evdev->cdev, evdev->dev.devt, 1); + if (error) + goto err_unregister_handle; + + /*将设备加入到Linux设备模型,它的内部将找到它的bus,然后让它的bus + 给它找到它的driver,在驱动或者总线的probe函数中,一般会在/dev/目录 + 先创建相应的设备节点,这样应用程序就可以通过该设备节点来使用设备了 + ,/dev/eventX 设备节点就是在此时生成 + */ + error = device_add(&evdev->dev); + if (error) + goto err_cleanup_evdev; + + return 0; + + err_cleanup_evdev: + evdev_cleanup(evdev); + err_unregister_handle: + input_unregister_handle(&evdev->handle); + err_free_evdev: + put_device(&evdev->dev); + err_free_minor: + input_free_minor(minor); + return error; +} +``` + +到这里,输入子系统的分析就结束了;如果还不是很了解,那么我们换个角度,应用层的调用如何作用到具体的实际硬件来分析,你就会清晰了。 + +## 从应用层的角度出发看input子系统 + +首先思考个问题,在应用层调用read函数,是如何读取到实际硬件的按键值的? +由上面分析可知,当调用open函数读取键值时,将调用到`evdev_read`: + +```c +static ssize_t evdev_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct evdev_client *client = file->private_data; + struct evdev *evdev = client->evdev; + struct input_event event; + size_t read = 0; + int error; + + if (count != 0 && count < input_event_size()) + return -EINVAL; + + for (;;) { + if (!evdev->exist || client->revoked) + return -ENODEV; + + /*如果client的环形缓冲区中没有数据并且是非阻塞的,那么返回-EAGAIN,也就是try again*/ + if (client->packet_head == client->tail && + (file->f_flags & O_NONBLOCK)) + return -EAGAIN; + + /* + * count == 0 is special - no IO is done but we check + * for error conditions (see above). + */ + if (count == 0) + break; + + /*调用evdev_fetch_next_event,如果获得了数据则取出来*/ + while (read + input_event_size() <= count && + evdev_fetch_next_event(client, &event)) { + + /*input_event_to_user调用copy_to_user传入用户程序中,这样读取完成*/ + if (input_event_to_user(buffer + read, &event)) + return -EFAULT; + + read += input_event_size(); + } + + if (read) + break; + + /*如果没有数据,并且是阻塞的,则在等待队列上等待*/ + if (!(file->f_flags & O_NONBLOCK)) { + error = wait_event_interruptible(evdev->wait, + client->packet_head != client->tail || + !evdev->exist || client->revoked); + if (error) + return error; + } + } + + return read; +} +``` + +如果read函数进入了休眠状态,又是谁来唤醒?搞明白了这个问题,也就知道了read的数据是从哪来的了。 +搜索这个evdev->wait这个等待队列变量,找到evdev_event函数里唤醒: + +```c +static void evdev_event(struct input_handle *handle, + unsigned int type, unsigned int code, int value) +{ + struct input_value vals[] = { { type, code, value } }; + + evdev_events(handle, vals, 1); + ---> evdev_pass_values(client, vals, count, ev_time); + ---> wake_up_interruptible(&evdev->wait); +} +``` + +其中evdev_event()是evdev.c(事件处理层) 的`evdev_handler->event`成员,如下图所示: + +![img](images/Linux输入子系统/14336242-dd6d91de64337029.png) + +猜测下,是谁调用evdev_event()这个`evdev_handler->event`事件驱动函数, + 应该就是之前分析的input_dev设备层调用的。 + input.c中试搜下`handler->event` 或 `handler.event`,回溯下函数调用: + handler->event(handle, v->type, v->code, v->value) + ---> input_to_handler + ---> input_pass_values + ---> input_handle_event + ---> *input_event* + 显然,就是input_dev通过输入核心为驱动层提供统一的接口,`input_event`,来向事件处理层上报数据并唤醒。 +``` + +`Linux/Linux开发/kernel/ecryptfs调试笔记.md`: + +```md +# ecryptfs调试笔记 + + + +环境: + +linux内核:5.3.13 + + + +## ecryptfs_mount + + + +```c +static struct dentry *ecryptfs_mount(struct file_system_type *fs_type, int flags, + const char *dev_name, void *raw_data) +``` + + + +- fs_type: 传进来的是之前定义的新的文件系统类型指针 + + ```c + static struct file_system_type ecryptfs_fs_type = { + .owner = THIS_MODULE, + .name = "ecryptfs", + .mount = ecryptfs_mount, + .kill_sb = ecryptfs_kill_block_super, + .fs_flags = 0 + }; + ``` + +- flags:0 +- dev_name: `"/home/superman/Desktop/realdir"` 文件系统挂载的路径 +- raw_data:`"ecryptfs_sig=cbd6dc63028e5602,ecryptfs_cipher=aes,ecryptfs_key_bytes=16,ecryptfs_unlink_sigs"` 这里是在应用层挂载时,传入的相关参数,如使用的算法,密钥长度等信息 + + + + + +```c + sbi = kmem_cache_zalloc(ecryptfs_sb_info_cache, GFP_KERNEL); + if (!sbi) { + rc = -ENOMEM; + goto out; + } +``` + +分配内存 + + + +```c +/* superblock private data. */ +struct ecryptfs_sb_info { + struct super_block *wsi_sb; + struct ecryptfs_mount_crypt_stat mount_crypt_stat; +}; +``` + + + +```c + rc = ecryptfs_parse_options(sbi, raw_data, &check_ruid); + if (rc) { + err = "Error parsing options"; + goto out; + } +``` + +解析raw_data中传进来的参数: + +- 初始化sbi的加密状态 +- 解析参数中的信息,填入加密状态结构体中 +- 先从一个链表之中,根据当前的加密算法进行查找,如果没有的话,就添加 + + + +```c + mount_crypt_stat = &sbi->mount_crypt_stat; + + s = sget(fs_type, NULL, set_anon_super, flags, NULL); + if (IS_ERR(s)) { + rc = PTR_ERR(s); + goto out; + } + + rc = super_setup_bdi(s); + if (rc) + goto out1; + + ecryptfs_set_superblock_private(s, sbi); +``` + +`sget`返回一个super_block,当没有存在的时候就创建一个 + + + +把super_block_info结构存在sb结构的`s_fs_info`私有区域中 + + + +```c + sbi = NULL; + s->s_op = &ecryptfs_sops; + s->s_xattr = ecryptfs_xattr_handlers; + s->s_d_op = &ecryptfs_dops; +``` + +填充超级块操作指针等 + + + +```c + err = "Reading sb failed"; + rc = kern_path(dev_name, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &path); + if (rc) { + ecryptfs_printk(KERN_WARNING, "kern_path() failed\n"); + goto out1; + } + if (path.dentry->d_sb->s_type == &ecryptfs_fs_type) { + rc = -EINVAL; + printk(KERN_ERR "Mount on filesystem of type " + "eCryptfs explicitly disallowed due to " + "known incompatibilities\n"); + goto out_free; + } +``` + + + +通过挂载的路径`/home/superman/Desktop/realdir`,使用`kern_path`获得path结构 + +```sh +(gdb) p *path.dentry +$32 = {d_flags = 2637888, d_seq = {sequence = 2}, d_hash = {next = 0xffff8880b6edb5c8, pprev = 0xffff8880ba784c40}, + d_parent = 0xffff8880b33cd980, d_name = {{{hash = 556860248, len = 7}, hash_len = 30621631320}, + name = 0xffff8880b0ae09f8 "realdir"}, d_inode = 0xffff8880b0ac5980, d_iname = "realdir", '\000' , d_lockref = {{ + lock_count = 21474836480, {lock = {{rlock = {raw_lock = {{val = {counter = 0}, {locked = 0 '\000', pending = 0 '\000'}, { + locked_pending = 0, tail = 0}}}}}}, count = 5}}}, d_op = 0x0 , d_sb = 0xffff888034951000, + d_time = 0, d_fsdata = 0x0 , {d_lru = {next = 0xffff8880b0abcb00, prev = 0xffff8880b0ae1040}, + d_wait = 0xffff8880b0abcb00}, d_child = {next = 0xffff8880b0ae1050, prev = 0xffff8880b33cda20}, d_subdirs = { + next = 0xffff8880b0abd950, prev = 0xffff8880b0abcb10}, d_u = {d_alias = {next = 0x0 , + pprev = 0xffff8880b0ac5ab8}, d_in_lookup_hash = {next = 0x0 , pprev = 0xffff8880b0ac5ab8}, d_rcu = { + next = 0x0 , func = 0xffff8880b0ac5ab8}}} + +``` + + + +```sh +(gdb) p path.dentry->d_sb->s_type +$33 = (struct file_system_type *) 0xffffffff827224c0 + +``` + +当前目录项所属的超级块中记录的文件系统类型是ext4类型 + + + +```c + if (check_ruid && !uid_eq(d_inode(path.dentry)->i_uid, current_uid())) { + rc = -EPERM; + printk(KERN_ERR "Mount of device (uid: %d) not owned by " + "requested user (uid: %d)\n", + i_uid_read(d_inode(path.dentry)), + from_kuid(&init_user_ns, current_uid())); + goto out_free; + } + + ecryptfs_set_superblock_lower(s, path.dentry->d_sb); + + +static inline void +ecryptfs_set_superblock_lower(struct super_block *sb, + struct super_block *lower_sb) +{ + ((struct ecryptfs_sb_info *)sb->s_fs_info)->wsi_sb = lower_sb; +} +``` + + + +在当前超级块私有数据中(指向的是sb info),记录下底层的文件系统。(加密文件系统是建立在已有的文件系统之上的) + + + +```c + /** + * Set the POSIX ACL flag based on whether they're enabled in the lower + * mount. + */ + s->s_flags = flags & ~SB_POSIXACL; + s->s_flags |= path.dentry->d_sb->s_flags & SB_POSIXACL; + + /** + * Force a read-only eCryptfs mount when: + * 1) The lower mount is ro + * 2) The ecryptfs_encrypted_view mount option is specified + */ + if (sb_rdonly(path.dentry->d_sb) || mount_crypt_stat->flags & ECRYPTFS_ENCRYPTED_VIEW_ENABLED) + s->s_flags |= SB_RDONLY; + + s->s_maxbytes = path.dentry->d_sb->s_maxbytes; + s->s_blocksize = path.dentry->d_sb->s_blocksize; + s->s_magic = ECRYPTFS_SUPER_MAGIC; + s->s_stack_depth = path.dentry->d_sb->s_stack_depth + 1; + + rc = -EINVAL; + if (s->s_stack_depth > FILESYSTEM_MAX_STACK_DEPTH) { + pr_err("eCryptfs: maximum fs stacking depth exceeded\n"); + goto out_free; + } + +``` + +按照ext4超级块中的数据,填充到当前超级块中来 + +注意`s_stack_depth`这里有`+1` + + + +```c + inode = ecryptfs_get_inode(d_inode(path.dentry), s); + rc = PTR_ERR(inode); + if (IS_ERR(inode)) + goto out_free; + + s->s_root = d_make_root(inode); + if (!s->s_root) { + rc = -ENOMEM; + goto out_free; + } + + +static struct inode *__ecryptfs_get_inode(struct inode *lower_inode, + struct super_block *sb) +{ + struct inode *inode; + + if (lower_inode->i_sb != ecryptfs_superblock_to_lower(sb)) + return ERR_PTR(-EXDEV); + if (!igrab(lower_inode)) + return ERR_PTR(-ESTALE); + inode = iget5_locked(sb, (unsigned long)lower_inode, + ecryptfs_inode_test, ecryptfs_inode_set, + lower_inode); + if (!inode) { + iput(lower_inode); + return ERR_PTR(-EACCES); + } + if (!(inode->i_state & I_NEW)) + iput(lower_inode); + + return inode; +} + + +static int ecryptfs_inode_set(struct inode *inode, void *opaque) +{ + struct inode *lower_inode = opaque; + + ecryptfs_set_inode_lower(inode, lower_inode); + fsstack_copy_attr_all(inode, lower_inode); + /* i_size will be overwritten for encrypted regular files */ + fsstack_copy_inode_size(inode, lower_inode); + inode->i_ino = lower_inode->i_ino; + inode->i_mapping->a_ops = &ecryptfs_aops; + + if (S_ISLNK(inode->i_mode)) + inode->i_op = &ecryptfs_symlink_iops; + else if (S_ISDIR(inode->i_mode)) + inode->i_op = &ecryptfs_dir_iops; + else + inode->i_op = &ecryptfs_main_iops; + + if (S_ISDIR(inode->i_mode)) + inode->i_fop = &ecryptfs_dir_fops; + else if (special_file(inode->i_mode)) + init_special_inode(inode, inode->i_mode, inode->i_rdev); + else + inode->i_fop = &ecryptfs_main_fops; + + return 0; +} +``` + +`ecryptfs_get_inode`函数传入的参数是当前目录项在ext4上的inode结构,和当前加密文件系统的超级块,是为了在当前加密文件系统超级块上伪造一个和ext4上一样的inode + + + +内部调用`__ecryptfs_get_inode`函数创建了一个新的节点,并通过`ecryptfs_inode_set`给新inode节点设置了相关的操作函数及文件操作相关函数。 + + + +```c + rc = -ENOMEM; + root_info = kmem_cache_zalloc(ecryptfs_dentry_info_cache, GFP_KERNEL); + if (!root_info) + goto out_free; + + /* ->kill_sb() will take care of root_info */ + ecryptfs_set_dentry_private(s->s_root, root_info); + root_info->lower_path = path; + + s->s_flags |= SB_ACTIVE; + return dget(s->s_root); + + +static inline void +ecryptfs_set_dentry_private(struct dentry *dentry, + struct ecryptfs_dentry_info *dentry_info) +{ + dentry->d_fsdata = dentry_info; +} +``` + + + +把自建dentry info结构体记录到dentry私有结构上。 + +自建目录项信息记录底层path结构 + + + +```c +out_free: + path_put(&path); +out1: + deactivate_locked_super(s); +out: + if (sbi) { + ecryptfs_destroy_mount_crypt_stat(&sbi->mount_crypt_stat); + kmem_cache_free(ecryptfs_sb_info_cache, sbi); + } + printk(KERN_ERR "%s; rc = [%d]\n", err, rc); + return ERR_PTR(rc); +``` + +失败清理工作 + + + +以上mount函数分析完毕 + + + +## 文件操作 + +```c + const struct file_operations ecryptfs_dir_fops = { + .iterate_shared = ecryptfs_readdir, + .read = generic_read_dir, + .unlocked_ioctl = ecryptfs_unlocked_ioctl, + #ifdef CONFIG_COMPAT + .compat_ioctl = ecryptfs_compat_ioctl, + #endif + .open = ecryptfs_dir_open, + .release = ecryptfs_dir_release, + .fsync = ecryptfs_fsync, + .llseek = ecryptfs_dir_llseek, + }; + + const struct file_operations ecryptfs_main_fops = { + .llseek = generic_file_llseek, + .read_iter = ecryptfs_read_update_atime, + .write_iter = generic_file_write_iter, + .unlocked_ioctl = ecryptfs_unlocked_ioctl, + #ifdef CONFIG_COMPAT + .compat_ioctl = ecryptfs_compat_ioctl, + #endif + .mmap = ecryptfs_mmap, + .open = ecryptfs_open, + .flush = ecryptfs_flush, + .release = ecryptfs_release, + .fsync = ecryptfs_fsync, + .fasync = ecryptfs_fasync, + .splice_read = generic_file_splice_read, + }; +``` + +在linux内核中,文件的操作由一个`file_operations`结构体表示,其中成员是各种文件操作函数指针。 + + + +### ecryptfs_open + + + +```c +static int ecryptfs_open(struct inode *inode, struct file *file) +``` + + + +- inode: 对应文件的inode节点 +- file: 对应的file结构 + +```sh +Thread 665 hit Breakpoint 1, ecryptfs_open (inode=0xffff8880a16e9a40, file=0xffff88809669a500) at fs/ecryptfs/file.c:192 +192 { +(gdb) p *file +$1 = {f_u = {fu_llist = {next = 0x0 }, fu_rcuhead = {next = 0x0 , + func = 0x0 }}, f_path = {mnt = 0xffff8880b0307720, dentry = 0xffff8880b63d5140}, + f_inode = 0xffff8880a16e9a40, f_op = 0xffffffff82063760 , f_lock = {{rlock = {raw_lock = {{val = {counter = 0}, { + locked = 0 '\000', pending = 0 '\000'}, {locked_pending = 0, tail = 0}}}}}}, f_write_hint = WRITE_LIFE_NOT_SET, + f_count = {counter = 1}, f_flags = 32768, f_mode = 32797, f_pos_lock = {owner = {counter = 0}, wait_lock = {{rlock = {raw_lock = {{ + val = {counter = 0}, {locked = 0 '\000', pending = 0 '\000'}, {locked_pending = 0, tail = 0}}}}}}, osq = {tail = { + counter = 0}}, wait_list = {next = 0xffff88809669a558, prev = 0xffff88809669a558}}, f_pos = 0, f_owner = {lock = {raw_lock = {{ + cnts = {counter = 0}, {wlocked = 0 '\000', __lstate = "\000\000"}}, wait_lock = {{val = {counter = 0}, {locked = 0 '\000', + pending = 0 '\000'}, {locked_pending = 0, tail = 0}}}}}, pid = 0x0 , pid_type = PIDTYPE_PID, uid = { + val = 0}, euid = {val = 0}, signum = 0}, f_cred = 0xffff8880a9888600, f_ra = {start = 0, size = 0, async_size = 0, ra_pages = 0, + mmap_miss = 0, prev_pos = 0}, f_version = 0, f_security = 0xffff8880966f43f0, private_data = 0x0 , + f_ep_links = {next = 0xffff88809669a5d0, prev = 0xffff88809669a5d0}, f_tfile_llink = {next = 0xffff88809669a5e0, + prev = 0xffff88809669a5e0}, f_mapping = 0xffff8880a16e9bb0, f_wb_err = 0} + +``` + + + + + +```c + int rc = 0; + struct ecryptfs_crypt_stat *crypt_stat = NULL; + struct dentry *ecryptfs_dentry = file->f_path.dentry; + /* Private value of ecryptfs_dentry allocated in + * ecryptfs_lookup() */ + struct ecryptfs_file_info *file_info; + + /* Released in ecryptfs_release or end of function if failure */ + file_info = kmem_cache_zalloc(ecryptfs_file_info_cache, GFP_KERNEL); + ecryptfs_set_file_private(file, file_info); + if (!file_info) { + ecryptfs_printk(KERN_ERR, + "Error attempting to allocate memory\n"); + rc = -ENOMEM; + goto out; + } +``` + +每打开一个文件便创建一个自建文件信息结构体,将该结构体和file结构的私有数据关联上。 + + + +```c + crypt_stat = &ecryptfs_inode_to_private(inode)->crypt_stat; + mutex_lock(&crypt_stat->cs_mutex); +// 新建的节点加密状态是空的,第一次打开时,会给设置上加密标志 + if (!(crypt_stat->flags & ECRYPTFS_POLICY_APPLIED)) { + ecryptfs_printk(KERN_DEBUG, "Setting flags for stat...\n"); + /* Policy code enabled in future release */ + crypt_stat->flags |= (ECRYPTFS_POLICY_APPLIED + | ECRYPTFS_ENCRYPTED); + } + mutex_unlock(&crypt_stat->cs_mutex); + +static inline struct ecryptfs_inode_info * +ecryptfs_inode_to_private(struct inode *inode) +{ + return container_of(inode, struct ecryptfs_inode_info, vfs_inode); +} +``` + + + +#### inode和ecryptfs_inode_info关系解释 + +需要看下`ecryptfs_inode_to_private`为什么`inode`地址会和`ecryptfs_inode_info`地址相关? + +在`ecryptfs_mount`函数中有这么一行代码: + +```c +s->s_op = &ecryptfs_sops; + +// ecryptfs_sops具体内容: + const struct super_operations ecryptfs_sops = { + .alloc_inode = ecryptfs_alloc_inode, + .destroy_inode = ecryptfs_destroy_inode, + .free_inode = ecryptfs_free_inode, + .statfs = ecryptfs_statfs, + .remount_fs = NULL, + .evict_inode = ecryptfs_evict_inode, + .show_options = ecryptfs_show_options + }; + +``` + + + +在`ecryptfs_alloc_inode`函数中有相关代码: + +```c + static struct inode *ecryptfs_alloc_inode(struct super_block *sb) + { + struct ecryptfs_inode_info *inode_info; + struct inode *inode = NULL; + + inode_info = kmem_cache_alloc(ecryptfs_inode_info_cache, GFP_KERNEL); + if (unlikely(!inode_info)) + goto out; + if (ecryptfs_init_crypt_stat(&inode_info->crypt_stat)) { + kmem_cache_free(ecryptfs_inode_info_cache, inode_info); + goto out; + } + mutex_init(&inode_info->lower_file_mutex); + atomic_set(&inode_info->lower_file_count, 0); + inode_info->lower_file = NULL; + inode = &inode_info->vfs_inode; + out: + return inode; + } +``` + +在这个函数中,会直接创建一个`inode_info`,其中的`vfs_inode`就是文件操作中所使用的`inode`节点。 + + + +```c + rc = ecryptfs_get_lower_file(ecryptfs_dentry, inode); + if (rc) { + printk(KERN_ERR "%s: Error attempting to initialize " + "the lower file for the dentry with name " + "[%pd]; rc = [%d]\n", __func__, + ecryptfs_dentry, rc); + goto out_free; + } +// lower_file是file*类型,f_flags是文件打开时的标志,这是比较底层文件打开是只读,当前打开标志不是只读,则退出 + if ((ecryptfs_inode_to_private(inode)->lower_file->f_flags & O_ACCMODE) + == O_RDONLY && (file->f_flags & O_ACCMODE) != O_RDONLY) { + rc = -EPERM; + printk(KERN_WARNING "%s: Lower file is RO; eCryptfs " + "file must hence be opened RO\n", __func__); + goto out_put; + } + ecryptfs_set_file_lower( + file, ecryptfs_inode_to_private(inode)->lower_file); + rc = read_or_initialize_metadata(ecryptfs_dentry); + if (rc) + goto out_put; + ecryptfs_printk(KERN_DEBUG, "inode w/ addr = [0x%p], i_ino = " + "[0x%.16lx] size: [0x%.16llx]\n", inode, inode->i_ino, + (unsigned long long)i_size_read(inode)); + goto out; + out_put: + ecryptfs_put_lower_file(inode); + out_free: + kmem_cache_free(ecryptfs_file_info_cache, + ecryptfs_file_to_private(file)); + out: + return rc; +``` + + + +`ecryptfs_get_lower_file`通过`ecryptfs_dentry`来填充`inode`中的`lower_file`结构 + +并通过`ecryptfs_set_file_lower`将`file`的私有数据指针指向下层的file结构 + +`read_or_initialize_metadata` 函数读取文件的元数据,如文件标题信息、属性等 + + + +#### read_or_initialize_metadata + +```c + static int read_or_initialize_metadata(struct dentry *dentry) + { + struct inode *inode = d_inode(dentry); + struct ecryptfs_mount_crypt_stat *mount_crypt_stat; + struct ecryptfs_crypt_stat *crypt_stat; + int rc; + + crypt_stat = &ecryptfs_inode_to_private(inode)->crypt_stat; + // 这是获取了加密文件系统的超级块加密状态 + mount_crypt_stat = &ecryptfs_superblock_to_private( + inode->i_sb)->mount_crypt_stat; + mutex_lock(&crypt_stat->cs_mutex); + + if (crypt_stat->flags & ECRYPTFS_POLICY_APPLIED && + crypt_stat->flags & ECRYPTFS_KEY_VALID) { + rc = 0; + goto out; + } + + rc = ecryptfs_read_metadata(dentry); + if (!rc) + goto out; + + if (mount_crypt_stat->flags & ECRYPTFS_PLAINTEXT_PASSTHROUGH_ENABLED) { + crypt_stat->flags &= ~(ECRYPTFS_I_SIZE_INITIALIZED + | ECRYPTFS_ENCRYPTED); + rc = 0; + goto out; + } + + if (!(mount_crypt_stat->flags & ECRYPTFS_XATTR_METADATA_ENABLED) && + !i_size_read(ecryptfs_inode_to_lower(inode))) { + rc = ecryptfs_initialize_file(dentry, inode); + if (!rc) + goto out; + } + + rc = -EIO; + out: + mutex_unlock(&crypt_stat->cs_mutex); + return rc; + } +``` + + + +#### ecryptfs_read_metadata分析 + + + +### ecryptfs_release + +```c + static int ecryptfs_release(struct inode *inode, struct file *file) + { + ecryptfs_put_lower_file(inode); + kmem_cache_free(ecryptfs_file_info_cache, + ecryptfs_file_to_private(file)); + return 0; + } +``` + + + + + +### ecryptfs_dir_open + +```c + static int ecryptfs_dir_open(struct inode *inode, struct file *file) + { + struct dentry *ecryptfs_dentry = file->f_path.dentry; + /* Private value of ecryptfs_dentry allocated in + * ecryptfs_lookup() */ + struct ecryptfs_file_info *file_info; + struct file *lower_file; + + /* Released in ecryptfs_release or end of function if failure */ + file_info = kmem_cache_zalloc(ecryptfs_file_info_cache, GFP_KERNEL); + ecryptfs_set_file_private(file, file_info); + if (unlikely(!file_info)) { + ecryptfs_printk(KERN_ERR, + "Error attempting to allocate memory\n"); + return -ENOMEM; + } + lower_file = dentry_open(ecryptfs_dentry_to_lower_path(ecryptfs_dentry), + file->f_flags, current_cred()); + if (IS_ERR(lower_file)) { + printk(KERN_ERR "%s: Error attempting to initialize " + "the lower file for the dentry with name " + "[%pd]; rc = [%ld]\n", __func__, + ecryptfs_dentry, PTR_ERR(lower_file)); + kmem_cache_free(ecryptfs_file_info_cache, file_info); + return PTR_ERR(lower_file); + } + ecryptfs_set_file_lower(file, lower_file); + return 0; + } +``` + + + +核心就是`dentry_open`函数打开底层的目录项 + + + +### ecryptfs_fasync + +```c + static int ecryptfs_fasync(int fd, struct file *file, int flag) + { + int rc = 0; + struct file *lower_file = NULL; + + lower_file = ecryptfs_file_to_lower(file); + if (lower_file->f_op->fasync) + rc = lower_file->f_op->fasync(fd, lower_file, flag); + return rc; + } +``` + +该函数用于打或关闭异步I/O的通告信号 + +### ecryptfs_fsync + +```c + static int + ecryptfs_fsync(struct file *file, loff_t start, loff_t end, int datasync) + { + int rc; + + rc = file_write_and_wait(file); + if (rc) + return rc; + + return vfs_fsync(ecryptfs_file_to_lower(file), datasync); + } +``` + +将给定文件的所有被缓存的数据写回磁盘。由系统调用。 + + + +## 超级块操作 + + + +```c + const struct super_operations ecryptfs_sops = { + .alloc_inode = ecryptfs_alloc_inode, + .destroy_inode = ecryptfs_destroy_inode, + .free_inode = ecryptfs_free_inode, + .statfs = ecryptfs_statfs, + .remount_fs = NULL, + .evict_inode = ecryptfs_evict_inode, + .show_options = ecryptfs_show_options + }; +``` + + + + + +### ecryptfs_alloc_inode + +```c + static struct inode *ecryptfs_alloc_inode(struct super_block *sb) + { + struct ecryptfs_inode_info *inode_info; + struct inode *inode = NULL; + + inode_info = kmem_cache_alloc(ecryptfs_inode_info_cache, GFP_KERNEL); + if (unlikely(!inode_info)) + goto out; + if (ecryptfs_init_crypt_stat(&inode_info->crypt_stat)) { + kmem_cache_free(ecryptfs_inode_info_cache, inode_info); + goto out; + } + mutex_init(&inode_info->lower_file_mutex); + atomic_set(&inode_info->lower_file_count, 0); + inode_info->lower_file = NULL; + inode = &inode_info->vfs_inode; + out: + return inode; + } +``` + + + +```c + /* inode private data. */ + struct ecryptfs_inode_info { + struct inode vfs_inode; + struct inode *wii_inode; + struct mutex lower_file_mutex; + atomic_t lower_file_count; + struct file *lower_file; + struct ecryptfs_crypt_stat crypt_stat; + }; +``` + + + +每创建一个inode节点,便申请分配一个`ecryptfs_inode_info`结构体 + + + +注意`inode_info->lower_file = NULL;`这里,`lower_file`并未设置,在`ecryptfs_open`中会被设置。 + + + +## 节点操作 + + + +```c + const struct inode_operations ecryptfs_symlink_iops = { + .get_link = ecryptfs_get_link, + .permission = ecryptfs_permission, + .setattr = ecryptfs_setattr, + .getattr = ecryptfs_getattr_link, + .listxattr = ecryptfs_listxattr, + }; + + const struct inode_operations ecryptfs_dir_iops = { + .create = ecryptfs_create, + .lookup = ecryptfs_lookup, + .link = ecryptfs_link, + .unlink = ecryptfs_unlink, + .symlink = ecryptfs_symlink, + .mkdir = ecryptfs_mkdir, + .rmdir = ecryptfs_rmdir, + .mknod = ecryptfs_mknod, + .rename = ecryptfs_rename, + .permission = ecryptfs_permission, + .setattr = ecryptfs_setattr, + .listxattr = ecryptfs_listxattr, + }; + + const struct inode_operations ecryptfs_main_iops = { + .permission = ecryptfs_permission, + .setattr = ecryptfs_setattr, + .getattr = ecryptfs_getattr, + .listxattr = ecryptfs_listxattr, + }; +``` + + + +### ecryptfs_create + +```c + static int + ecryptfs_create(struct inode *directory_inode, struct dentry *ecryptfs_dentry, + umode_t mode, bool excl) + { + struct inode *ecryptfs_inode; + int rc; + + ecryptfs_inode = ecryptfs_do_create(directory_inode, ecryptfs_dentry, + mode); + if (IS_ERR(ecryptfs_inode)) { + ecryptfs_printk(KERN_WARNING, "Failed to create file in" + "lower filesystem\n"); + rc = PTR_ERR(ecryptfs_inode); + goto out; + } + /* At this point, a file exists on "disk"; we need to make sure + * that this on disk file is prepared to be an ecryptfs file */ + rc = ecryptfs_initialize_file(ecryptfs_dentry, ecryptfs_inode); + if (rc) { + ecryptfs_do_unlink(directory_inode, ecryptfs_dentry, + ecryptfs_inode); + iget_failed(ecryptfs_inode); + goto out; + } + d_instantiate_new(ecryptfs_dentry, ecryptfs_inode); + out: + return rc; + } +``` + +VFS通过系统调用`create`和`open`来调用该函数,从而为dentry对象创建一个新的索引节点。在创建时使用mode指定的初始模式。 + + + +为什么超级块上已经有alloc_inode的功能了,inode操作函数还要有创建inode的函数: + +其实在内部调用了: + +```c + inode = iget5_locked(sb, (unsigned long)lower_inode, + ecryptfs_inode_test, ecryptfs_inode_set, +``` + + + + + +此函数在内部调用了`vfs_create`调用底层文件系统进行创建inode。之后进行初始化。`ecryptfs_initialize_file`函数内部对inode进行加密状态的初始化,密钥,ctx等初始化操作后,用` ecryptfs_write_metadata`函数对节点或目录项写入元数据。 + + + +最后创建一个新的缓存。 + + + +### ecryptfs_setattr + +```c +static int ecryptfs_setattr(struct dentry *dentry, struct iattr *ia) +{ + int rc = 0; + struct dentry *lower_dentry; + struct iattr lower_ia; + struct inode *inode; + struct inode *lower_inode; + struct ecryptfs_crypt_stat *crypt_stat; + + // 先判断当前节点的加密状态,如果没有进行初始化,需要先初始化状态 + crypt_stat = &ecryptfs_inode_to_private(d_inode(dentry))->crypt_stat; + if (!(crypt_stat->flags & ECRYPTFS_STRUCT_INITIALIZED)) { + rc = ecryptfs_init_crypt_stat(crypt_stat); + if (rc) + return rc; + } + inode = d_inode(dentry); + lower_inode = ecryptfs_inode_to_lower(inode); + lower_dentry = ecryptfs_dentry_to_lower(dentry); + mutex_lock(&crypt_stat->cs_mutex); + // 如果是文件夹,则把加密状态取消 + if (d_is_dir(dentry)) + crypt_stat->flags &= ~(ECRYPTFS_ENCRYPTED); + // 如果是通常的文件 + else if (d_is_reg(dentry) + && (!(crypt_stat->flags & ECRYPTFS_POLICY_APPLIED) + || !(crypt_stat->flags & ECRYPTFS_KEY_VALID))) { + struct ecryptfs_mount_crypt_stat *mount_crypt_stat; + + // 取出超级块的加密信息来 + mount_crypt_stat = &ecryptfs_superblock_to_private( + dentry->d_sb)->mount_crypt_stat; + rc = ecryptfs_get_lower_file(dentry, inode); + if (rc) { + mutex_unlock(&crypt_stat->cs_mutex); + goto out; + } + rc = ecryptfs_read_metadata(dentry); + ecryptfs_put_lower_file(inode); + if (rc) { + if (!(mount_crypt_stat->flags + & ECRYPTFS_PLAINTEXT_PASSTHROUGH_ENABLED)) { + rc = -EIO; + printk(KERN_WARNING "Either the lower file " + "is not in a valid eCryptfs format, " + "or the key could not be retrieved. " + "Plaintext passthrough mode is not " + "enabled; returning -EIO\n"); + mutex_unlock(&crypt_stat->cs_mutex); + goto out; + } + rc = 0; + crypt_stat->flags &= ~(ECRYPTFS_I_SIZE_INITIALIZED + | ECRYPTFS_ENCRYPTED); + } + } + mutex_unlock(&crypt_stat->cs_mutex); + + rc = setattr_prepare(dentry, ia); + if (rc) + goto out; + if (ia->ia_valid & ATTR_SIZE) { + rc = ecryptfs_inode_newsize_ok(inode, ia->ia_size); + if (rc) + goto out; + } + + memcpy(&lower_ia, ia, sizeof(lower_ia)); + if (ia->ia_valid & ATTR_FILE) + lower_ia.ia_file = ecryptfs_file_to_lower(ia->ia_file); + if (ia->ia_valid & ATTR_SIZE) { + rc = truncate_upper(dentry, ia, &lower_ia); + if (rc < 0) + goto out; + } + + /* + * mode change is for clearing setuid/setgid bits. Allow lower fs + * to interpret this in its own way. + */ + if (lower_ia.ia_valid & (ATTR_KILL_SUID | ATTR_KILL_SGID)) + lower_ia.ia_valid &= ~ATTR_MODE; + + inode_lock(d_inode(lower_dentry)); + rc = notify_change(lower_dentry, &lower_ia, NULL); + inode_unlock(d_inode(lower_dentry)); +out: + fsstack_copy_attr_all(inode, lower_inode); + return rc; +} +``` + + + +修改更新一个节点的元数据,底层节点也修改。 +``` + +`Linux/Linux开发/kernel/ecryptfs重要函数.txt`: + +```txt +ecryptfs_inode_set 在mount时被调用,此函数设置节点的相关操作函数 + + +文件操作结构体: + +const struct file_operations ecryptfs_dir_fops = { + .iterate_shared = ecryptfs_readdir, + .read = generic_read_dir, + .unlocked_ioctl = ecryptfs_unlocked_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = ecryptfs_compat_ioctl, +#endif + .open = ecryptfs_dir_open, + .release = ecryptfs_dir_release, + .fsync = ecryptfs_fsync, + .llseek = ecryptfs_dir_llseek, +}; + +const struct file_operations ecryptfs_main_fops = { + .llseek = generic_file_llseek, + .read_iter = ecryptfs_read_update_atime, + .write_iter = generic_file_write_iter, + .unlocked_ioctl = ecryptfs_unlocked_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = ecryptfs_compat_ioctl, +#endif + .mmap = ecryptfs_mmap, + .open = ecryptfs_open, + .flush = ecryptfs_flush, + .release = ecryptfs_release, + .fsync = ecryptfs_fsync, + .fasync = ecryptfs_fasync, + .splice_read = generic_file_splice_read, +}; + + +节点操作结构体: + +const struct inode_operations ecryptfs_symlink_iops = { + .get_link = ecryptfs_get_link, + .permission = ecryptfs_permission, + .setattr = ecryptfs_setattr, + .getattr = ecryptfs_getattr_link, + .listxattr = ecryptfs_listxattr, +}; + +const struct inode_operations ecryptfs_dir_iops = { + .create = ecryptfs_create, + .lookup = ecryptfs_lookup, + .link = ecryptfs_link, + .unlink = ecryptfs_unlink, + .symlink = ecryptfs_symlink, + .mkdir = ecryptfs_mkdir, + .rmdir = ecryptfs_rmdir, + .mknod = ecryptfs_mknod, + .rename = ecryptfs_rename, + .permission = ecryptfs_permission, + .setattr = ecryptfs_setattr, + .listxattr = ecryptfs_listxattr, +}; + +const struct inode_operations ecryptfs_main_iops = { + .permission = ecryptfs_permission, + .setattr = ecryptfs_setattr, + .getattr = ecryptfs_getattr, + .listxattr = ecryptfs_listxattr, +}; + + +超级块操作: + +const struct super_operations ecryptfs_sops = { + .alloc_inode = ecryptfs_alloc_inode, + .destroy_inode = ecryptfs_destroy_inode, + .free_inode = ecryptfs_free_inode, + .statfs = ecryptfs_statfs, + .remount_fs = NULL, + .evict_inode = ecryptfs_evict_inode, + .show_options = ecryptfs_show_options +}; +``` + +`Linux/Linux开发/kernel/file_system_type.md`: + +```md +# file_system_type + +此结构体用于文件系统的注册 + + + +这里的文件系统是指可能会被挂载到目录树中的各个实际文件系统,所谓实际文件系统,即是指VFS 中的实际操作最终要通过它们来完成而已,并不意味着它们一定要存在于某种特定的存储设备上。比如在笔者的 Linux 机器下就注册有 "rootfs"、"proc"、"ext2"、"sockfs" 等十几种文件系统。 + + + +```c +struct file_system_type { + const char *name; + int fs_flags; + int (*get_sb) (struct file_system_type *, int, + const char *, void *, struct vfsmount *); + void (*kill_sb) (struct super_block *); + struct module *owner; + struct file_system_type * next; + struct list_head fs_supers; + + struct lock_class_key s_lock_key; + struct lock_class_key s_umount_key; + + struct lock_class_key i_lock_key; + struct lock_class_key i_mutex_key; + struct lock_class_key i_mutex_dir_key; + struct lock_class_key i_alloc_sem_key; +}; +``` + + + +注册过程实际上将表示各实际文件系统的 `struct file_system_type `数据结构的实例化,然后形成一个链表,内核中用一个名为 `file_systems` 的全局变量来指向该链表的表头。 + + + +- name:文件系统的名字,这个名字唯一的标识一种文件系统; +- next:为文件系统的链表指针; +- fs_supers:对于每一个mount的文件系统,系统都会为它创建一个super_block数据结构,该结构保存文件系统本身以及挂载点相关的信息。由于可以同时挂载多个同一文件系统类型的文件系统(比如/ 和/home都挂载了ext3文件系统),因此同一个文件系统类型会对应多个super block,@fs_supers就把这个文件系统类型对应的super block链接起来。 +- owner是指向module的指针,仅当文件系统类型是以模块方式注册时,owner才有效。 + + + + +``` + +`Linux/Linux开发/kernel/kmem_cache.md`: + +```md +# kmem_cache + + + +```c +struct kmem_cache_order_objects { + unsigned long x; +}; + +/* + * Slab cache management. + */ +struct kmem_cache { + /* Used for retriving partial slabs etc */ + unsigned long flags; + int size; /* The size of an object including meta data */ + int objsize; /* The size of an object without meta data */ + int offset; /* Free pointer offset. */ + struct kmem_cache_order_objects oo; + + /* + * Avoid an extra cache line for UP, SMP and for the node local to + * struct kmem_cache. + */ + struct kmem_cache_node local_node; + + /* Allocation and freeing of slabs */ + struct kmem_cache_order_objects max; + struct kmem_cache_order_objects min; + gfp_t allocflags; /* gfp flags to use on each alloc */ + int refcount; /* Refcount for slab cache destroy */ + void (*ctor)(void *); + int inuse; /* Offset to metadata */ + int align; /* Alignment */ + unsigned long min_partial; + const char *name; /* Name (only for display!) */ + struct list_head list; /* List of slab caches */ +#ifdef CONFIG_SLUB_DEBUG + struct kobject kobj; /* For sysfs */ +#endif + +#ifdef CONFIG_NUMA + /* + * Defragmentation by allocating from a remote node. + */ + int remote_node_defrag_ratio; + struct kmem_cache_node *node[MAX_NUMNODES]; +#endif +#ifdef CONFIG_SMP + struct kmem_cache_cpu *cpu_slab[NR_CPUS]; +#else + struct kmem_cache_cpu cpu_slab; +#endif +}; +``` + + + +在Linux系统中,伙伴系统(buddy system)是以页为单位管理和分配内存的。但是现实的需求却以字节为单位,假如我们需要申请20bytes,总不能分配一页吧!那岂不是严重浪费内存。那么该如何分配呢?slab分配器就应运而生了,专为小内存分配而生。slab分配器分配内存以Byte为单位。但是slab分配器并没有脱离伙伴系统,而是基于伙伴系统分配的大内存进一步细分成小内存分配。 + + + +现在假如从伙伴系统分配一页内存供slab分配器管理。对于每个slab分配器来说,就是将这段连续内存平均分成若干大小相等的object进行管理。可以我们总得知道每一个object的size吧!管理的内存页数也是需要知道的吧!不然怎么知道如何分配呢!因此需要一个数据结构管理。那就是struct kmem_cache。 + + + +- flags:object分配掩码,例如经常使用的`SLAB_HWCACHE_ALIGN`标志位,代表创建的kmem_cache管理的object按照硬件cache对齐,一切都是为了速度。 +- size:分配的object size +- objsize:实际的object size,就是创建`kmem_cache`时候传递进来的参数。和size的关系就是,size是各种地址对齐之后的大小。因此,size要大于等于objsize +- offset:slab分配在管理object的时候采用的方法是:既然每个object在没有分配之前不在乎每个object中存储的内容,那么完全可以在每个object中存储下一个object内存首地址,就形成了一个单链表。很巧妙的设计。那么这个地址数据存储在object什么位置呢?offset就是存储下个object地址数据相对于这个object首地址的偏移。 +- oo:低16位代表一个slab中所有object的数量(oo&((1 << 16) - 1)),高16位代表一个slab管理的page数量((2^(oo 16))pages) +- max:看了代码好像就是等于oo +- min:当按照大小分配内存的时候出现内存不足就会考虑min大小方式分配。min只需要可以容纳一个object即可 +- allocflags:从伙伴系统分配内存掩码 +- inuse:objsize按照word对齐之后的大小 +- name:sysfs文件系统显示使用 +- list:系统有一个slab_caches链表,所有的slab都会挂入此链表 +- node:slab节点。在NUMA系统中,每个node都有一个`struct kmem_cache_node`数据结构。 + + + +## 接口 + +```c +struct kmem_cache * +kmem_cache_create (const char *name, size_t size, size_t align, + unsigned long flags, void (*ctor)(void *)) +``` + + + +`keme_cache_create`是创建`kmem_cache`数据结构,参数描述如下: + +- name:`kmem_cache`的名称 +- size:slba管理对象的大小 +- align:slab分配器分配内存的对齐字节数(以align字节对齐) +- flags:分配内存掩码 +- ctor:分配对象的构造回调函数 + +`kmem_cache_destroy`的作用和`kmem_cache_create`相反,就是销毁创建的`kmem_cache`。 + +`kmem_cache_alloc`是从cachep参数指定的kmem_cache管理的内存缓存池中分配一个对象,其中flags是分配掩码,GFP_KERNEL是不是很熟悉的掩码? + +`kmem_cache_free`是`kmem_cache_alloc`的反操作 + + + +## 接口如何使用 + +slab分配器提供的接口该如何使用呢?其实很简单,总结分成以下几个步骤: + +1. `kmem_cache_create`创建一个`kmem_cache`数据结构。 +2. 使用`kmem_cache_alloc`接口分配内存,`kmem_cache_free`接口释放内存。 +3. release第一步创建的`kmem_cache`数据结构。 + +demo: + +```c +/* + * This is a demo for how to use kmem_cache_create + */ +void slab_demo(void) +{ + struct kmem_cache *kmem_cache_16 = kmem_cache_create("kmem_cache_16", 16, + 8, ARCH_KMALLOC_FLAGS, + NULL); + + /* now you can alloc memory, the buf points to 16 bytes of memory*/ + char *buf = kmeme_cache_alloc(kmem_cache_16, GFP_KERNEL); + + /* + * do something what you what, don't forget to release the memory after use +*/ + kmem_cache_free(kmem_cache_16, buf); + + kmem_cache_destroy(kmem_cache_16); +} +``` + + + +1. 首先使用`kmem_cache_create`创建名称为`kmem_cache_16`的`kmem_cache`,该`kmem_cache`主要是描述如何管理一堆对象,其实就是slab的布局。**每个对象都是16字节,并且分配的对象地址按照8字节对齐,也就是说从kmem_cache_16中分配的对象大小全是16字节。不管你要申请多少,反正就是16Bytes。**当然,`kmem_cache_create`仅仅是创建了一个描述slab缓存池布局的数据结构,并没有从伙伴系统申请内存,具体的申请内存操作是在`kmeme_cache_alloc`中完成的。 +2. `kmeme_cache_alloc`从`kmem_cache_16`分配一个对象。 +3. 内存使用结束记得`kmem_cache_free`释放。 +4. 如果不需要这个`kmem_cache`的话,就可以调用`kmem_cache_destroy`进行销毁吧。在释放`kmem_cache`之前要保证从该`kmem_cache`中分配的对象全部释放了,否则无法释放`kmem_cache`。 + + + +## 数据结构之间的关系 + +什么是slab缓存池呢?我的理解就是使用`struct kmem_cache`结构描述的一段内存就称作一个slab缓存池。一个slab缓存池就像是一箱牛奶,一箱牛奶中有很多瓶牛奶,每瓶牛奶就是一个object。分配内存的时候,就相当于从牛奶箱中拿一瓶。总有拿完的一天。当箱子空的时候,你就需要去超市再买一箱回来。超市就相当于partial链表,超市存储着很多箱牛奶。如果超市也卖完了,自然就要从厂家进货,然后出售给你。厂家就相当于伙伴系统。 + +![4a471520078976](https://raw.githubusercontent.com/supermanc88/ImageSources/master/4a471520078976.png) + + + +### slab管理object的方法 + +在图片的左上角就是一个slub缓存池中object的分布以及数据结构和kmem_cache之间的关系。首先一个slab缓存池包含的页数是由oo决定的。oo拆分为两部分,低16位代表一个slab缓存池中object的数量,高16位代表包含的页数。使用kmem_cache_create()接口创建kmem_cache的时候需要指出obj的size和对齐align。也就是传入的参数。kmem_cache_create()主要是就是填充kmem_cache结构体成员。既然从伙伴系统得到(2^(oo >> 16)) pages大小内存,按照size大小进行平分。一般来说都不会整除,因此剩下的就是图中灰色所示。由于每一个object的大小至少8字节,当然可以用来存储下一个object的首地址。就像图中所示的,形成单链表。图中所示下个obj地址存放的位置位于每个obj首地址处,在内核中称作指针内置式。同时,下个obj地址存放的位置和obj首地址之间的偏移存储在kmem_cache的offset成员。两外一种方式是指针外置式,即下个obj的首地址存储的位置位于obj尾部,也就是在obj尾部再分配sizeof(void *)字节大小的内存。对于外置式则offset就等于kmem_cache的inuse成员。 + + + + + +http://www.wowotech.net/memory_management/426.html + + + + +``` + +`Linux/Linux开发/kernel/match_token.md`: + +```md +# match_token函数解析 + +源码如下: + +```c +/** + * match_one: - Determines if a string matches a simple pattern + * @s: the string to examine for presense of the pattern + * @p: the string containing the pattern + * @args: array of %MAX_OPT_ARGS &substring_t elements. Used to return match + * locations. + * + * Description: Determines if the pattern @p is present in string @s. Can only + * match extremely simple token=arg style patterns. If the pattern is found, + * the location(s) of the arguments will be returned in the @args array. + */ +static int match_one(char *s, const char *p, substring_t args[]) +{ + // 比如传进来的是 s: pid=1 + // p:pid=%d + char *meta; + int argc = 0; + + if (!p) + return 1; + + while(1) { + int len = -1; + meta = strchr(p, '%'); + if (!meta) + return strcmp(p, s) == 0; + + if (strncmp(p, s, meta-p)) + return 0; + + // 如果%之前的内容都一样的话 + s += meta - p; // s定位到1的位置 + p = meta + 1; // p定位到d的位置 + + if (isdigit(*p)) + len = simple_strtoul(p, (char **) &p, 10); + else if (*p == '%') { + if (*s++ != '%') + return 0; + p++; + continue; + } + // *p不是数字 也不是% + + if (argc >= MAX_OPT_ARGS) + return 0; + + args[argc].from = s; + switch (*p++) { // 现在 *p是d + case 's': + if (strlen(s) == 0) + return 0; + else if (len == -1 || len > strlen(s)) + len = strlen(s); + args[argc].to = s + len; + break; + case 'd': + simple_strtol(s, &args[argc].to, 0); // args[argc].to 返回NULL + goto num; + case 'u': + simple_strtoul(s, &args[argc].to, 0); + goto num; + case 'o': + simple_strtoul(s, &args[argc].to, 8); + goto num; + case 'x': + simple_strtoul(s, &args[argc].to, 16); + num: + if (args[argc].to == args[argc].from) + return 0; + break; + default: + return 0; + } + s = args[argc].to; // s = NULL + argc++; + } +} + +/** + * match_token: - Find a token (and optional args) in a string + * @s: the string to examine for token/argument pairs + * @table: match_table_t describing the set of allowed option tokens and the + * arguments that may be associated with them. Must be terminated with a + * &struct match_token whose pattern is set to the NULL pointer. + * @args: array of %MAX_OPT_ARGS &substring_t elements. Used to return match + * locations. + * + * Description: Detects which if any of a set of token strings has been passed + * to it. Tokens can include up to MAX_OPT_ARGS instances of basic c-style + * format identifiers which will be taken into account when matching the + * tokens, and whose locations will be returned in the @args array. + */ +int match_token(char *s, const match_table_t table, substring_t args[]) +{ + const struct match_token *p; + + for (p = table; !match_one(s, p->pattern, args) ; p++) + // 看样子是 match_one 返回不为0时,循环中止 + ; + + return p->token; +} +``` + + + +```c +/* associates an integer enumerator with a pattern string. */ +struct match_token { + int token; + const char *pattern; +}; + +typedef struct match_token match_table_t[]; + +/* Maximum number of arguments that match_token will find in a pattern */ +enum {MAX_OPT_ARGS = 3}; + +/* Describe the location within a string of a substring */ +typedef struct { + char *from; + char *to; +} substring_t; +``` + + + +```c +static const match_table_t tokens = { + {ecryptfs_opt_sig, "sig=%s"}, + {ecryptfs_opt_ecryptfs_sig, "ecryptfs_sig=%s"}, + {ecryptfs_opt_cipher, "cipher=%s"}, + {ecryptfs_opt_ecryptfs_cipher, "ecryptfs_cipher=%s"}, + {ecryptfs_opt_ecryptfs_key_bytes, "ecryptfs_key_bytes=%u"}, + {ecryptfs_opt_passthrough, "ecryptfs_passthrough"}, + {ecryptfs_opt_xattr_metadata, "ecryptfs_xattr_metadata"}, + {ecryptfs_opt_encrypted_view, "ecryptfs_encrypted_view"}, + {ecryptfs_opt_fnek_sig, "ecryptfs_fnek_sig=%s"}, + {ecryptfs_opt_fn_cipher, "ecryptfs_fn_cipher=%s"}, + {ecryptfs_opt_fn_cipher_key_bytes, "ecryptfs_fn_key_bytes=%u"}, + {ecryptfs_opt_unlink_sigs, "ecryptfs_unlink_sigs"}, + {ecryptfs_opt_check_dev_ruid, "ecryptfs_check_dev_ruid"}, + {ecryptfs_opt_err, NULL} +}; +``` + + +``` + +`Linux/Linux开发/kernel/struct page.md`: + +```md +# struct page + +以下为page的结构体: + +```c +/* + * Each physical page in the system has a struct page associated with + * it to keep track of whatever it is we are using the page for at the + * moment. Note that we have no way to track which tasks are using + * a page, though if it is a pagecache page, rmap structures can tell us + * who is mapping it. + * + * If you allocate the page using alloc_pages(), you can use some of the + * space in struct page for your own purposes. The five words in the main + * union are available, except for bit 0 of the first word which must be + * kept clear. Many users use this word to store a pointer to an object + * which is guaranteed to be aligned. If you use the same storage as + * page->mapping, you must restore it to NULL before freeing the page. + * + * If your page will not be mapped to userspace, you can also use the four + * bytes in the mapcount union, but you must call page_mapcount_reset() + * before freeing it. + * + * If you want to use the refcount field, it must be used in such a way + * that other CPUs temporarily incrementing and then decrementing the + * refcount does not cause problems. On receiving the page from + * alloc_pages(), the refcount will be positive. + * + * If you allocate pages of order > 0, you can use some of the fields + * in each subpage, but you may need to restore some of their values + * afterwards. + * + * SLUB uses cmpxchg_double() to atomically update its freelist and + * counters. That requires that freelist & counters be adjacent and + * double-word aligned. We align all struct pages to double-word + * boundaries, and ensure that 'freelist' is aligned within the + * struct. + */ +#ifdef CONFIG_HAVE_ALIGNED_STRUCT_PAGE +#define _struct_page_alignment __aligned(2 * sizeof(unsigned long)) +#else +#define _struct_page_alignment +#endif + +struct page { + unsigned long flags; /* Atomic flags, some possibly + * updated asynchronously */ + /* + * Five words (20/40 bytes) are available in this union. + * WARNING: bit 0 of the first word is used for PageTail(). That + * means the other users of this union MUST NOT use the bit to + * avoid collision and false-positive PageTail(). + */ + union { + struct { /* Page cache and anonymous pages */ + /** + * @lru: Pageout list, eg. active_list protected by + * pgdat->lru_lock. Sometimes used as a generic list + * by the page owner. + */ + struct list_head lru; + /* See page-flags.h for PAGE_MAPPING_FLAGS */ + struct address_space *mapping; + pgoff_t index; /* Our offset within mapping. */ + /** + * @private: Mapping-private opaque data. + * Usually used for buffer_heads if PagePrivate. + * Used for swp_entry_t if PageSwapCache. + * Indicates order in the buddy system if PageBuddy. + */ + unsigned long private; + }; + struct { /* page_pool used by netstack */ + /** + * @dma_addr: might require a 64-bit value even on + * 32-bit architectures. + */ + dma_addr_t dma_addr; + }; + struct { /* slab, slob and slub */ + union { + struct list_head slab_list; + struct { /* Partial pages */ + struct page *next; +#ifdef CONFIG_64BIT + int pages; /* Nr of pages left */ + int pobjects; /* Approximate count */ +#else + short int pages; + short int pobjects; +#endif + }; + }; + struct kmem_cache *slab_cache; /* not slob */ + /* Double-word boundary */ + void *freelist; /* first free object */ + union { + void *s_mem; /* slab: first object */ + unsigned long counters; /* SLUB */ + struct { /* SLUB */ + unsigned inuse:16; + unsigned objects:15; + unsigned frozen:1; + }; + }; + }; + struct { /* Tail pages of compound page */ + unsigned long compound_head; /* Bit zero is set */ + + /* First tail page only */ + unsigned char compound_dtor; + unsigned char compound_order; + atomic_t compound_mapcount; + }; + struct { /* Second tail page of compound page */ + unsigned long _compound_pad_1; /* compound_head */ + unsigned long _compound_pad_2; + struct list_head deferred_list; + }; + struct { /* Page table pages */ + unsigned long _pt_pad_1; /* compound_head */ + pgtable_t pmd_huge_pte; /* protected by page->ptl */ + unsigned long _pt_pad_2; /* mapping */ + union { + struct mm_struct *pt_mm; /* x86 pgds only */ + atomic_t pt_frag_refcount; /* powerpc */ + }; +#if ALLOC_SPLIT_PTLOCKS + spinlock_t *ptl; +#else + spinlock_t ptl; +#endif + }; + struct { /* ZONE_DEVICE pages */ + /** @pgmap: Points to the hosting device page map. */ + struct dev_pagemap *pgmap; + void *zone_device_data; + /* + * ZONE_DEVICE private pages are counted as being + * mapped so the next 3 words hold the mapping, index, + * and private fields from the source anonymous or + * page cache page while the page is migrated to device + * private memory. + * ZONE_DEVICE MEMORY_DEVICE_FS_DAX pages also + * use the mapping, index, and private fields when + * pmem backed DAX files are mapped. + */ + }; + + /** @rcu_head: You can use this to free a page by RCU. */ + struct rcu_head rcu_head; + }; + + union { /* This union is 4 bytes in size. */ + /* + * If the page can be mapped to userspace, encodes the number + * of times this page is referenced by a page table. + */ + atomic_t _mapcount; + + /* + * If the page is neither PageSlab nor mappable to userspace, + * the value stored here may help determine what this page + * is used for. See page-flags.h for a list of page types + * which are currently stored here. + */ + unsigned int page_type; + + unsigned int active; /* SLAB */ + int units; /* SLOB */ + }; + + /* Usage count. *DO NOT USE DIRECTLY*. See page_ref.h */ + atomic_t _refcount; + +#ifdef CONFIG_MEMCG + struct mem_cgroup *mem_cgroup; +#endif + + /* + * On machines where all RAM is mapped into kernel address space, + * we can simply calculate the virtual address. On machines with + * highmem some memory is mapped into kernel virtual memory + * dynamically, so we need a place to store that address. + * Note that this field could be 16 bits on x86 ... ;) + * + * Architectures with slow multiplication can define + * WANT_PAGE_VIRTUAL in asm/page.h + */ +#if defined(WANT_PAGE_VIRTUAL) + void *virtual; /* Kernel virtual address (NULL if + not kmapped, ie. highmem) */ +#endif /* WANT_PAGE_VIRTUAL */ + +#ifdef LAST_CPUPID_NOT_IN_PAGE_FLAGS + int _last_cpupid; +#endif +} _struct_page_alignment; +``` + + + +page代表系统内存的最小单位。 + + + +`struct page`中使用了大量的联合体。 + + + +```c + struct { /* Page cache and anonymous pages */ + /** + * @lru: Pageout list, eg. active_list protected by + * pgdat->lru_lock. Sometimes used as a generic list + * by the page owner. + */ + struct list_head lru; + /* See page-flags.h for PAGE_MAPPING_FLAGS */ + struct address_space *mapping; + pgoff_t index; /* Our offset within mapping. */ + /** + * @private: Mapping-private opaque data. + * Usually used for buffer_heads if PagePrivate. + * Used for swp_entry_t if PageSwapCache. + * Indicates order in the buddy system if PageBuddy. + */ + unsigned long private; + }; +``` + +文件系统主要用的是这个结构体 + + + +- index: 在映射的虚拟空间内的偏移,一个文件可能只映射一部分,假设映射了1M的空间,index指的是在1M空间内的偏移,而不是在整个文件内的偏移。 + +- mapping: + + ```c + /** + * http://opengrok.theworktips.cloud/source/xref/linux-5.3.13/include/linux/fs.h#442 + * struct address_space - Contents of a cacheable, mappable object. + * 可缓存、可映射对象的内容 + * @host: Owner, either the inode or the block_device. + * 内容的所有者,即文件节点 + * @i_pages: Cached pages. + * 缓存的页面 + * @gfp_mask: Memory allocation flags to use for allocating pages. + * 用于分配页面的内存分配标志。 + * @i_mmap_writable: Number of VM_SHARED mappings. + * @i_mmap: Tree of private and shared mappings. + * @i_mmap_rwsem: Protects @i_mmap and @i_mmap_writable. + * @nrpages: Number of page entries, protected by the i_pages lock. + * @nrexceptional: Shadow or DAX entries, protected by the i_pages lock. + * @writeback_index: Writeback starts here. + * @a_ops: Methods. + * @flags: Error bits and flags (AS_*). + * @wb_err: The most recent error which has occurred. + * @private_lock: For use by the owner of the address_space. + * @private_list: For use by the owner of the address_space. + * @private_data: For use by the owner of the address_space. + */ + struct address_space { + struct inode *host; + struct xarray i_pages; + gfp_t gfp_mask; + atomic_t i_mmap_writable; + struct rb_root_cached i_mmap; + struct rw_semaphore i_mmap_rwsem; + unsigned long nrpages; + unsigned long nrexceptional; + pgoff_t writeback_index; + const struct address_space_operations *a_ops; + unsigned long flags; + errseq_t wb_err; + spinlock_t private_lock; + struct list_head private_list; + void *private_data; + } __attribute__((aligned(sizeof(long)))) __randomize_layout; + ``` + + + +mapping指定了页帧所在的地址空间, index是页帧在映射内部的偏移量. 地址空间是一个非常一般的概念. 例如, 可以用在向内存读取文件时. 地址空间用于将文件的内容与装载数据的内存区关联起来. mapping不仅能够保存一个指针, 而且还能包含一些额外的信息, 用于判断页是否属于未关联到地址空间的某个匿名内存区. + +1. 如果mapping = 0,说明该page属于交换高速缓存页(swap cache);当需要使用地址空间时会指定交换分区的地址空间swapper_space。 +2. 如果mapping != 0,第0位bit[0] = 0,说明该page属于页缓存或文件映射,mapping指向文件的地址空间address_space。 +3. 如果mapping != 0,第0位bit[0] != 0,说明该page为匿名映射,mapping指向struct anon_vma对象。 + + + +## 重点成员结构 + +- index + + 在映射的虚拟空间(vma_area)内的偏移;一个文件可能只映射了一部分,假设映射了1M的空间,index指的是在1M空间内的偏移,而不是在整个文件内的偏移。单位是**SHIFT_SIZE**,就是当前系统一个页的大小。为0的话,就是1M空间的0位置;为1的话就是1M空间内的4096位置。 + +- flag + + 用来存放页的状态。这些状态包括页是不是脏的,是不是被锁定在内存中等。flag中的每一位单独表示一种状态,所以它至少可以同时表示出32种不同的状态。 + +- count + + 存放页的引用计数——也就量这一页被引用了多少次。当计数值变为-1时,就说明当前内核并没有引用这一页,于是,在新的分配中就可以使用它了。 + +- virtual + + 页的虚拟地址。通常情况下,它就是页在虚拟内存中的地址。有些内存(即所谓的高端内存)并不永久地映射到内核地址空间上。在这种情况下,这个域的值为NULL,需要的时候,必须动态地映射这些页。 +``` + +`Linux/Linux开发/kernel/task_struct.md`: + +```md +# task_struct + + + +## 主要成员 + +- pid + + pid 每个 task_struct 都会有一个不同的 ID,就是这个 PID。 + +- tid + + tid 线程 ID,用来标识每个线程的。 + +- tgid + + tgid 线程组领头线程的 PID,事实上就是主线程的 PID。 当创建一个子进程时,它的 tgid 与 pid 相等; 当创建一个线程时,它的 tgid 等于主线程的 pid。 + + getpid() 函数事实上返回的是当前进程或线程的 tgid。 + +- pgid + + pgid 进程组领头进程的 PID。 + +- sid + + sid 会话领头进程的 PID。 + +- group_leader + + group_leader 是一个 task_struct 类型的指针,指向的是进程组的组长对应的 task_struct 对象。 + + + +sys.c + +```c +SYSCALL_DEFINE0(getpid) +{ + return task_tgid_vnr(current); +} + +SYSCALL_DEFINE0(getppid) +{ + int pid; + + rcu_read_lock(); + pid = task_tgid_vnr(rcu_dereference(current->real_parent)); + rcu_read_unlock(); + + return pid; +} +``` + + +``` + +`Linux/Linux开发/kernel/write_begin_write_end.md`: + +```md +# write_begin write_end + +路径:mm/filemap.c + +generic_perform_write + +代码节选: + +```c +status = a_ops->write_begin(file, mapping, pos, bytes, flags, +&page, &fsdata); +if (unlikely(status < 0)) +break; + +if (mapping_writably_mapped(mapping)) +flush_dcache_page(page); + +copied = iov_iter_copy_from_user_atomic(page, i, offset, bytes); +flush_dcache_page(page); + +status = a_ops->write_end(file, mapping, pos, bytes, copied, +page, fsdata); + +... + +balance_dirty_pages_ratelimited(mapping); +``` + + + +`write_begin`为写操作准备page页,可以看到`&page`是输入输出类型的。函数由底层的文件系统实现,主要处理需要额外申请的空间,以及从文件中读取不在缓存里的数据,这个函数准备好后, + +调用`iov_iter_copy_from_user_atomic`函数将用户层的数据写到`page`中, + +之后调用`write_end`更新inode大小,并将`page`标记为`dirty`。 + +最后调用`balance_dirty_pages_ratelimited`函数平衡内存中的脏页,需要时将脏页刷盘。 + + +``` + +`Linux/Linux开发/kernel/内存管理.md`: + +```md +# 内存管理 + +内核把物理页作为内存管理的基本单位。 + + + +内存管理单元 MMU + + + +MMU以页(page)大小为单位来和粳系统中的页表 + + + +在 32 操作系统中,其虚拟地址为 32 位长度,因此其虚拟地址空间的范围为 `2 ^ 32 = 4GB`。Linux 系统将地址空间按 3:1 比例划分,其中用户空间(user space)占 3GB,内核空间(kernel space)占 1GB。Linux 系统进程的虚拟内存地址空间布局如下图所示: + + + +![img](images/内存管理/86f7099990ff736039089518e9000221.png) + + + +## 区 + +内核使用区对具有相似特性的页进行分组 + +Linux必须处理如下两种由于硬件存在缺陷而引起的内存寻址问题: + +- 一些硬件只能用某些特定的内存地址来执行DMA (直接内存访问) +- 一些体系结构的内存的物理寻址警署比虎势寻址范围大得多。这样,就有一些内存不能永久地映射到内核空间上 + + + +因为存在这些制约条件,Linux主要使用了四种区: + +- ZONE_DMA —— 这个区包含的页能用来执行DMA操作 +- ZONE_DMA32 —— 和ZONE_DMA类似,该区包含的页面可用来执行DMA操作;而和ZONE_DMA不同之处在于,这些页面只能被32位设备访问。 +- ZONE_NORMAL —— 这个区包含的都是能正常映射的页 +- ZONE_HIGHMEM —— 这个区包含`高端内存`,其中的页并不能永久的地映射到内核地址空间。 +- 等 + + + +区的实际使用和分布是与体系结构相关的。 + +例如,某些体系结构在内存的任何地址上执行DMA都没有问题。在这些体系结构中,ZONE_DMA为空,ZONE_NORMAL就可以直接用于分配。与此相反在x86体系结构上,ISA设备(ISA总线)就不能在整个32位的地址空间中执行DMA,因为ISA设备只能访问物理内存的前16MB。因此,ZONE_DMA在x86上包含的页都在0~16MB的内存范围里。 + + + +ZONE_HIGHMEM的工作方式也差不多。能否直接映射取决于体系结构。在32位的x86系统上,ZONE_HIGHMEM为高于896MB的所有物理内存。在其他体系结构上,由于所有内存都被直接映射,所以ZONE_HIGHMEM为空。ZONE_HIGHMEM所在的内存就是所谓的高端内存。系统的其余内存就是所谓的低端内存。 + + + +| 区 | 描述 | 物理内存 | +| ------------ | -------------- | ---------- | +| ZONE_DMA | DMA使用的页 | <16MB | +| ZONE_NORMAL | 正常可寻址的页 | 16 ~ 896MB | +| ZONE_HIGHMEM | 动态映射的页 | >896MB | + + + +并不是所有的体系结构都定义了全部区,有些64位的体系结构,如Intel的x86-64体系结构可以映射和处理64位的内存空间,所以x86-64没有ZONE_HIGHMEM区,所有的物理内存都处于ZONE_DMA和ZONE_NORMAL区。 + + + + + +## 获得页 + +内核提供了一种请求内存的底层机制,并提供了对它进行访问的几个接口。所有的接口以页为单位分配内存。 + +最核心的函数是: + +```c +struct page * alloc_pages(gfp_t gfp_mask, unsigned int order) +``` + +这里的order是指数 + +这个函数分配`2^order`个连续的物理页。如果成功返回指向第一个页的page结构体指针。 + +使用下面的函数将分配的页转换成逻辑地址: + +```c +void * page_address(struct page *page) +``` + +该函数返回一个指针,指向给定物理页当前所在的逻辑地址。 + + + +如果不想通过上面的转换过程: + +```c +unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order) +``` + +这个函数和alloc_pages作用一样,不过它直接返回所请求的第一个页的逻辑地址。 + + + +如果只需要一页,可以使用下面的函数: + +```c +struct page * alloc_page(gfp_t gfp_mask) + +unsigned long __get_free_page(gfp_t gfp_mask) +``` + +它们只不过忽略了第二个参数,直接传0了 + + + +1. 获得填充为0的页 + + ```c + unsigned long get_zeroed_page(gfp_t gfp_mask) + ``` + +2. 释放页 + + 函数起名真恶心,下划线都没有对应上 + + ```c + // 与 alloc_pages对应 + void __free_pages(struct page *page, unsigned int order) + + // 与 __get_free_pages对应 + void free_pages(unsigned long addr, unsigned int order) + + // 与 __get_free_page对应 + void free_page(unsigned long addr) + ``` + + + +## kmalloc + +kmalloc函数与用户空间的malloc一族函数非常相似。 + +kmalloc函数是一个简单的接口,用它可以获得以字节为单位的一块内核内存。对于大多数内核分配来说,kmalloc接口用得更多。 + + + +```c +void * kmalloc(size_t size, gfp_t flags) +``` + + + +## kfree + +kmalloc的另一端就是kfree + +```c +void free(const vod *ptr) +``` + +kfree函数释放由kmalloc分配出来的内存块。 + +注意,调用kfree(NULL)是安全的。 + + + +## vmalloc + +vmalloc函数工作方式与kmalloc类似。 + +只不过vmalloc分配的内存虚拟地址是连续的,而物理地址则无须连续。这也是用户空间分配函数的工作方式。不保证它们在物理RAM中也是连续的。 + +kmalloc函数确保页在物理地址上是连续的,虚拟地址自然也是连续的。 + +vmalloc通过分配非连续的物理内存块,再修正页表,把内存映射到逻辑地址空间的连续区域,就能做到这点 + +大多数情况下,只有硬件设备需要得到的物理地址是连续的内存。 + + + +尽管在某些情况下才需要物理上连续的内存块,但很多内核代码都用kmalloc。这主要是出于性能的考虑。vmalloc函数为了把物理上不连续的页转换为虚拟地址空间上连续的页,必须专门建立页表项。糟糕的是,通过vmalloc获得的页必须一个一个地进行映射,这就会导致比直接内存映射大得多的TLB抖动。因为这些原因,vmalloc仅在不得已的情况下才会使用——典型的就是为了获得大块的内存。 + +```c +void * vmalloc(unsigned long size) +``` + + + +## vfree + +```c +void vfree(const void * addr) +``` + + + +## gfp_mask标志 + + + +标志可分三类: + +- 行为修饰符 + + 行为修饰符表示内核应该如何分配所需的内存。在某些特定情况下,只能使用某些特定的方法分配内存。如,中断处理程序就要求内核在分配内存的过程中不能睡眠。 + +- 区修饰符 + + 区修饰符表示从哪儿分配内存。 + +- 类型 + + 类型标志组合了行为修饰符和区修饰符,将各种可能用到的组合归纳为不同类型,简化了修饰符的使用。这样,只需要一个类型修饰标志就可以了。如,GFP_KERNEL就是一种类型标志,内核中进程上下文相关的代码可以使用它。 + + + +常用标志类型: + +**GFP_ATOMIC** + +​ 这个标志用在中断处理程序、下半部、持有自旋锁以及其他不能睡眠的地方 + +**GFP_KERNEL** + +​ 这是一种常规分配方式,可能会阻塞。这个标志在睡眠这安全时用在进程上下文代码中。为了获得调用者所需的内存,内核会尽力而为。这个标志应当是首先标志 + +**GFP_USER** + +​ 这是一种常规分配方式,可能会阻塞。这个标志用于为用户空间进程分配内存时 + +**GFP_DMA** + +​ 这是从ZONE_DMA区进行分配。需要获取能供DMA使用的内存的设备驱动程序使用这个标志,通常与以上某个标志组合在一起使用 + + + +## slab层 + + +``` + +`Linux/Linux开发/kernel/内核调试/O0方式编译内核.md`: + +```md +# O0方式编译内核 + +[经过修改的内核](https://github.com/supermanc88/runninglinuxkernel_4.0) + +此内核经过特殊修改,仅对特定平台支持 + +## 安装编译工具链 + +环境:Ubuntu18.04 + +**注:不能使用ubuntu19或20版本,经测试,虽然可以编译内核,但不能加载,目前只确认了18版本可以正常使用** + +工具链:gcc5 + +```sh +apt-get install gcc-5 +apt-get install g++-5 +``` + + + +## 修改默认工具链 + +```shell +sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 100 +sudo update-alternatives --install /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-5 100 +sudo update-alternatives --install /usr/bin/gcc-nm gcc-nm /usr/bin/gcc-nm-5 100 +sudo update-alternatives --install /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-5 100 + +sudo update-alternatives --install /usr/bin/x86_64-linux-gnu-cpp x86_64-linux-gnu-cpp /usr/bin/x86_64-linux-gnu-cpp-5 100 +sudo update-alternatives --install /usr/bin/x86_64-linux-gnu-gcc x86_64-linux-gnu-gcc /usr/bin/x86_64-linux-gnu-gcc-5 100 +sudo update-alternatives --install /usr/bin/x86_64-linux-gnu-gcc-ar x86_64-linux-gnu-gcc-ar /usr/bin/x86_64-linux-gnu-gcc-ar-5 100 +sudo update-alternatives --install /usr/bin/x86_64-linux-gnu-gcc-nm x86_64-linux-gnu-gcc-nm /usr/bin/x86_64-linux-gnu-gcc-nm-5 100 +sudo update-alternatives --install /usr/bin/x86_64-linux-gnu-gcc-ranlib x86_64-linux-gnu-gcc-ranlib /usr/bin/x86_64-linux-gnu-gcc-ranlib-5 100 +sudo update-alternatives --install /usr/bin/x86_64-linux-gnu-gcov x86_64-linux-gnu-gcov /usr/bin/x86_64-linux-gnu-gcov-5 100 +sudo update-alternatives --install /usr/bin/x86_64-linux-gnu-gcov-dump x86_64-linux-gnu-gcov-dump /usr/bin/x86_64-linux-gnu-gcov-dump-5 100 +sudo update-alternatives --install /usr/bin/x86_64-linux-gnu-gcov-tool x86_64-linux-gnu-gcov-tool /usr/bin/x86_64-linux-gnu-gcov-tool-5 100 +``` + + + + + + + +## 编译内核 + +修改`_install_x86`文件夹名,不使用这个根文件系统,使用`create-image.sh`创建的文件系统。 + + + +编译: + +```sh +export ARCH=x86 +export CROSS_COMPILE=x86_64-linux-gnu- +make defconfig +make -j4 +``` + + +``` + +`Linux/Linux开发/kernel/内核调试/Thread overran stack.md`: + +```md +# Thread overran stack, or stack corrupted + +驱动开发遇到如下问题: + + + +![image-20201210114625948](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201210114625948.png) + + + +## 查看用户空间栈大小 + +```shell +ulimit -s // 这里显示的单位是kb +``` + +![image-20201210115543494](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201210115543494.png) + +所以实验机默认栈大小是8M大小 + + + +```shell +ulimit -a +``` + +![image-20201210115720611](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201210115720611.png) + + + + + +## 修改用户空间栈大小 + +临时修改: + +```shell +ulimit -s 102400 // 临时修改为100M +``` + + + +永久修改: + +``` +方法一:可以在/etc/rc.local 内加入 ulimit -s 102400 则可以开机就设置栈空间大小,任何用户启动的时候都会调用。 + +方法二:修改配置文件/etc/security/limits.conf +``` + + + +## 内核栈大小 + +```c +#define THREAD_SIZE_ORDER 1 +#define THREAD_SIZE (PAGE_SIZE << THREAD_SIZE_ORDER) +``` + +内核栈大小默认是8K,如果要修改的话,只能重新编译内核。 + + + +## crash定位问题 + +```shell +task +``` + + + +![image-20201210145833637](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201210145833637.png) + + + +```shell +thread_info 0xffff8800b91e4000 +``` + + + + + +![image-20201210145615228](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201210145615228.png) + +这些数据一看你很离谱了,cpu这么大的数,另外thread_info中的task的值上面的也不一样。 + + + + + +相关链接: + +[内核栈溢出 | Linux Performance](http://linuxperf.com/?p=116) + + +``` + +`Linux/Linux开发/kernel/内核调试/Ubuntu内核编译.md`: + +```md +# Ubuntu内核编译 + + + +## 下载对应的内核源码 + + + +先找到对应的内核源码: + +> https://wiki.ubuntu.com/Kernel/Dev/KernelGitGuide + +| | | +| ------- | ------------------------------------------------------------ | +| eoan | git://git.launchpad.net/~ubuntu-kernel/ubuntu/+source/linux/+git/eoan | +| disco | git://git.launchpad.net/~ubuntu-kernel/ubuntu/+source/linux/+git/disco | +| bionic | git://git.launchpad.net/~ubuntu-kernel/ubuntu/+source/linux/+git/bionic | +| xenial | git://git.launchpad.net/~ubuntu-kernel/ubuntu/+source/linux/+git/xenial | +| trusty | git://git.launchpad.net/~ubuntu-kernel/ubuntu/+source/linux/+git/trusty | +| precise | git://git.launchpad.net/~ubuntu-kernel/ubuntu/+source/linux/+git/precise | + + + + + +根据自己当前的Ubuntu版本下载对应的内核代码。 + +一般情况下,默认的master分支不是当前的Ubuntu对应的版本代码。 + +所以需要切换。 + + + +查看当前运行的Ubuntu的精确版本号: + +> cat /proc/version_signature +> +> Ubuntu 4.15.0-45.48-generic 4.15.18 + +查找对应的tag + +> git tag | grep 4.15.0 + +切换 + +> git checkout Ubuntu-4.15.0-45.48 + + + +## 编译内核 + + + +为什么要自己编译呢,主要是因为下载安装Ubuntu的 `'linux-image-'``$(uname ``-``r)``'-dbgsym'`,发现并不能进行调试。 + +所以还是自己编译的好。还有一个优点就是可以把自己想要调试的驱动直接编译进内核,比调试单独的模块更加简单一些。 + + + +编译和安装步骤如下 + +*a. make menuconfig* + +*b. make* + +*c. make modules* + +*d. make modules_install* + +*e. make install* + +*f. make headers_install* + +注意步**骤d和e**不能颠倒,否则生成的initrd.img不能正常启动,因为它依赖于**步骤c**生成的驱动程序.ko文件。 + + + +### 进入编译选项 + + + +```sh +make menuconfig +``` + +正常情况下是不需要改变什么的,需要有 + +- debug info +- kgdb:use kgdb over the serial console +- kernel debugging + + + +![image-20200901125732043](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200901125732043.png) + + + +#### 错误 + + + +error1: + +```sh +*** Unable to find the ncurses libraries or the +*** required header files. +*** 'make menuconfig' requires the ncurses libraries. +*** Install ncurses (ncurses-devel) and try again. +``` + +> sudo apt-get install ncurses-dev + + + +error2: + +```sh +/bin/sh: 1: flex: not found +scripts/Makefile.lib:194: recipe for target 'scripts/kconfig/lexer.lex.c' failed +make[1]: *** [scripts/kconfig/lexer.lex.c] Error 127 +Makefile:552: recipe for target 'menuconfig' failed +make: *** [menuconfig] Error 2 +``` + +> sudo apt-get install flex + + + +error3: + +```sh + LEX scripts/kconfig/lexer.lex.c + YACC scripts/kconfig/parser.tab.h +/bin/sh: 1: bison: not found +scripts/Makefile.lib:208: recipe for target 'scripts/kconfig/parser.tab.h' failed +make[1]: *** [scripts/kconfig/parser.tab.h] Error 127 +Makefile:552: recipe for target 'menuconfig' failed +make: *** [menuconfig] Error 2 +``` + +> sudo apt-get install bison + + + +error4: + +```sh +scripts/sign-file.c:25:10: fatal error: openssl/opensslv.h: 没有那个文件或目录 + #include + ^~~~~~~~~~~~~~~~~~~~ +compilation terminated. +scripts/Makefile.host:92: recipe for target 'scripts/sign-file' failed +make[1]: *** [scripts/sign-file] Error 1 +Makefile:1065: recipe for target 'scripts' failed +make: *** [scripts] Error 2 +``` + +> sudo apt-get install libssl-dev + + + +error5: + +```sh +warning: Cannot use CONFIG_STACK_VALIDATION=y, please install libelf-dev, libelf-devel or elfutils-libelf-devel +``` + +> apt-get install libelf-dev + + + +### 编译 + + + +#### 编译优化 + +```c +void __attribute__((optimize("O0"))) foo(unsigned char data) { + // unmodifiable compiler code +} +``` + +虽然内核编译的时候,不能使用`-O0`方式去代码优化,但可以把单独的几个函数去优化,只要给函数加上`__attribute__((optimize("O0")))`属性即可。 + + + +#### 开始编译 + +```sh +make +``` + +**注:虚拟机一般默认是20G大小,这显然是不够的,最好给40G大小** + + + +![image-20200901121401806](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200901121401806.png) + +![image-20200901121503588](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200901121503588.png) + +我这是2C2G的虚拟机配置,内核版本5.3.0,编译了将近2小时。 + + + +当出现`vmlinux`时,就说明就编译成功了。 + + + +### 安装 + +安装内核模块: + +```sh +make modules_install +``` + +![image-20200901130638749](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200901130638749.png) + +此命令会将所有的模块安装到内核 + +驱动驱动会安装到`/lib/modules` + +![image-20200901130859919](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200901131333515.png) + + + +安装新内核: + +```sh +make install +``` + +![image-20200901131649911](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200901130859919.png) + +编译的新内核会安装到:`/boot/` + +安装之前: + +![image-20200901131333515](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200901131649911.png) + + + +安装之后: + +![image-20200901131720707](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200901131830926.png) + + + +并在`/boot/grub/grub.cfg`中更新引导 + +![image-20200901131830926](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200901132042392.png) + + + +默认的引导改为了最新的内核了 + + + +如果要修改在开机时进入引导选择,修改`/etc/default/grub`文件: + +![image-20200901132042392](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200901131720707.png) + + + + + +## 调试 + +![image-20200901165952193](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200901165952193.png) + + + +出现这个的原因是:随机化基址 + +可以在`/boot/grub/grub.cfg`启动参数后加`rodata=off nokaslr` + + + +有的在输入 + +```c +echo g > /proc/sysrq-trigger +``` + +中断不生效,这是因为sysrq在ubuntu系统未开启: + +```c +echo 1 > /proc/sys/kernel/sysrq +``` + +使用上面命令开启 + + + +在调试的过程中,时间长了程序会自动中止,加上以下命令: + +```sh +echo 0 > /proc/sys/kernel/hung_task_timeout_secs +``` + + + + + +### gdb调试命令 + +```sh +set serial baud 115200 + +target remote /dev/ttyS0 +``` + + + + + +``` + +`Linux/Linux开发/kernel/内核调试/gdb调试内核模块ko.md`: + +```md +# 内核模块的调试方式 + + + +## 获取驱动模块的基址 + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/33695fd212954f2aa4dcf513ac40d5f7.png) + +```shell +cat /proc/modules | grep drv1 + + +cat /sys/module/drv1/section/.text +``` + + + +两种方式均可查到加载基地址。 + + + +## 设置调试模块 + + + +加载符号文件同时为驱动的Open函数设置断点,如下图所示 + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/57df302f6c58477bbc2854474bb94817.png) + + + +需要注意的是不管是图中的驱动文件还是驱动程序源文件都应该是位于主机中(要把被调试机编译好的驱动文件及源文件,拷贝一份给调试机),需要注意的是调试机中拷贝过来的驱动文件可以放置在任何位置,而源文件的位置应该和被调试机中源文件位置保持一致,这样才能进行源码级调试(上面黄框中源代码的位置表示是在被调试机中的位置,所以调试机驱动的源码也应该放在这样的位置,这样驱动调试器才能找到符号位置。如果对驱动程序不进行源码调试,只进行汇编调试就不需要源文件),而加载地址是被调试机的驱动加载基地址,这一点千万别搞错了。 + + + +**一句话就是,源文件路径一致,省事** +``` + +`Linux/Linux开发/kernel/内核调试/gdb调试命令.md`: + +```md +# gdb调试命令 + + + +## 断点 + +### 设置断点 + +```shell +b function_name +``` + +```shell +b *address +``` + +**注意:“*”号是必须加在地址前面的,否则会认为是行号** + +```shell +b line_num +``` + + + +指定文件下断点 + +```shell +b filename:line_num + +b filename:function +``` + + + +### 删除断点 + +```shell +clear 要清除的断点行号 +``` + +```shell +delete 要清除的断点编号 +``` + + + +### 禁用/启用断点 + +```shell +disable/enable 断点编号 +``` + + + +### 禁用全部断点 + +```shell +disable +``` + + + +### 查看断点 + +```shell +info b +``` + + + + + +## 变量 + +### 局部变量(当前stack frame) + +```shell +info locals +``` + + + +### 函数参数(当前stack frame) + +```shell +info args +``` + + + +### 打印变量 + +```shell +p variable +``` + + + +### 设置变量值 + +```shell +set 变量名 = 变量值 // 改变程序中某个变量的值 +``` + + + +### 变量类型 + +```shell +whatis 变量名 // 显示某个表达式的数据类型 +``` + + + +## 寄存器 + +### 显示寄存器 + +```shell +info registers +i r +info r +``` + +![image-20201208165151595](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201208165151595.png) + + + +### 访问寄存器 + +寄存器名前加`$`,进行访问 + +```shell +$rax +$rbx +... +$rip +... +``` + + + + + +## 打印信息 + +### 打印字符串 + +```shell +p *array@length // length是想要查看的长度 +``` + +### 打印结构体 + +如下sb为super_block结构指针,直接使用p打印会打印指针数值,并不会显示其中成员 + +![image-20201208164116488](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201208164116488.png) + +使用`p *sb`时,这样会显示详细的成员 + +![image-20201208164337983](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201208164337983.png) + + + +### 强制转换类型打印 + +下面的例子为打印rax寄存器内容,并强制转换成一个结构体指针打印 + +![image-20201208165828157](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201208165828157.png) + +```shell +p (type)arg +``` + + + + + +## 进程 + +```shell +info proc pid +``` + + + + + + + +## 流程控制 + + + +### 当前EIP或RIP + +当浏览代码,找不到正在调试的地方时,使用`where`命令进行查找当前调试位置: + +```shell +where +``` + + + +### 单步调试 + +```shell +next // 单步跟踪程序,当遇到函数调用时,也不进入此函数体 +n + +step // 单步调试如果有函数调用,则进入函数; +s +``` + + + +### 跳转到指定位置执行 + +**中间代码流程会被忽略?暂未确认** + +如果有时调试太快,错过了想要调试的位置,当你的代码环境支持时,你可以`jump`到那行,再执行一次。 + +需要注意的是: + +1. `jump`命令只改变pc的值,所以改变程序执行可能会出现不同的结果,比如变量i的值 +2. 通过(临时)断点的配合,可以让你的程序跳到指定的位置,并停下来 + +```shell +b 行号 +j 行号 +``` + + + +### 执行到当前函数返回 + +```shell +finish +``` + + + +### 强制返回当前函数 + +```shell +return +``` + + + +### 执行到指定位置 + +假如我们在25行停住了,现在想要运行到29行停住,就可以使用until命令(可简写为u): + +```shell +until 行号 +``` + + + +## 汇编 + +```shell +x + +display + +disassemble +``` + + + +## TUI模式 + +### 开启TUI模式 + +```shell +gdb --tui vmlinux +``` + +### 关闭TUI模式 + +``` +Ctrl + X + A +``` + +### 更换激活窗口 + +``` +Ctrl + X + O +``` + +### 窗口滚动 + +*PgUp* + +激活窗口的内容向上滚动一页 *Scroll the active window one page up.* + +*PgDn* + +激活窗口的内容向下滚动一页 *Scroll the active window one page down.* + +*Up* + +激活窗口的内容向上滚动一行 *Scroll the active window one line up.* + +*Down* + +激动窗口的内容向下滚动一行 *Scroll the active window one line down.* + +*Left* + +激活窗口的内容向左移动一列 *Scroll the active window one column left.* + +*Right* + +激活窗口的内容向右移动一列 *Scroll the active window one column right.* + +*C-L* + +更新屏幕 *Refresh the screen.* + + + + + +## 内核调试常用结构 + +### 文件系统 + +```shell +// 查看正在操作的文件名 +p file->f_path.dentry->d_iname + +p dentry->d_iname + +// 通过page获取inode信息 +p *page->mapping->host + +// 查看文件大小 +p inode->i_size + +p dentry->d_inode->i_size + + +``` + + + + +``` + +`Linux/Linux开发/kernel/内核调试/gef-gdb增强插件.md`: + +```md +# GEF - GDB Enhanced Features + + + +gef文档:https://gef.readthedocs.io/en/dev/ + +中文翻译文档:https://www.lhyerror404.cn/2019/05/29/gef-%E4%BD%BF%E7%94%A8%E6%89%8B%E5%86%8C/ + + + +## 安装 + +要求:gdb版本7.7及以上并且要支持python3 + +```shell +# via the install script +$ wget -q -O- https://github.com/hugsy/gef/raw/master/scripts/gef.sh | sh + +# manually +$ wget -O ~/.gdbinit-gef.py -q https://github.com/hugsy/gef/raw/master/gef.py +$ echo source ~/.gdbinit-gef.py >> ~/.gdbinit +``` + + + +项目链接:https://github.com/hugsy/gef + +*Note*: As of January 2020, GEF doesn't officially support Python 2 any longer, due to Python 2 becoming officially deprecated. If you really need GDB+Python2, use [`gef-legacy`](https://github.com/hugsy/gef-legacy) instead. + + + +如图为安装成功 + +![image-20201211175611650](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201211175611650.png) + + + +## 运行 + +```shell +gdb -q vmlinux +``` + + + +运行这个插件之后,会有一些慢 + + + +linux内核调试使用这个插件的话,感觉不是很好 + + + +## 编辑布局 + +```shell +gef> gef config context.layout +---------------------------- GEF configuration setting: context.layout ------------------------------------- +context.layout (str) = "legend regs stack code args source memory threads trace extra" +``` + + + +目前有6个部分可以显示: + +- `legend` : 颜色代码的文字说明 +- `regs` : 寄存器的状态 +- `stack` : `$sp` 寄存器指向的内存内容 +- `code` : 正在执行的代码 +- `args` : 如果在函数调用处停止,则打印调用参数 +- `source` : 如果用source编译,这将显示相应的源代码行 +- `threads` : 所有线程 +- `trace` : 执行调用跟踪 +- `extra` : 如果检测到漏洞(易受攻击的格式字符串,堆漏洞等),它将显示在此窗格中 +- `memory` : 查看任意内存位置 + +要隐藏一个部分,只需使用`context.layout`设置,并在部分名称前加上`-`或者省略它。 + +``` +gef➤ gef config context.layout "-legend regs stack code args -source -threads -trace extra memory" +``` + +此配置不会显示`source`,`threads`和`trace`部分。 + + + +我自己的配置,不要颜色说明、线程显示和漏洞检测 + +```shell +gef config context.layout "-legend regs stack code args source memory -threads trace -extra" +``` + + + +![image-20201211183754742](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201211183754742.png) + + + +经过一段时间的使用,发现 `regs`、`stack`、`memory`这几个也不怎么常用,所以最终的配置为: + +```shell +gef config context.layout "-legend -regs -stack code args source -memory -threads trace -extra" +``` + + + +有的时候`trace`这项也会很耗费时间,按需使用。 + + + + + +## 显示效果 + +结构体的显示也比只用gdb显示好多了 + +![image-20201217114218500](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201217114218500.png) +``` + +`Linux/Linux开发/kernel/内核调试/其他文件.md`: + +```md +# 一些基本的文件解释 + + + +## System.map文件 + +存在于`/boot/`目录下。 + +该文件是是一份内核符号表kernel symbol table,包含了内核中的变量名和函数名地址,在每次编译内核时,自动生成。 + +在[Linux](https://zh.m.wikipedia.org/wiki/Linux)中,**System.map**文件是被[内核](https://zh.m.wikipedia.org/wiki/内核)所使用的**[符号表](https://zh.m.wikipedia.org/wiki/符号表)**。 + +符号表是一个在符号名称与它们的存储器位置间的查询表格。符号名称可能是变量的名称或是函数名称。当要查询符号名称的位置或是特定位置的符号名称时,就会需要System.map。对于[内核错误](https://zh.m.wikipedia.org/wiki/内核错误)及[内核oops](https://zh.m.wikipedia.org/wiki/Linux内核oops)的调试特别有用。当`CONFIG_KALLSYMS`激活时,核心会自行做位置到名称的转换,所以像是ksymoops这一类的工具并不是必要的。 + + + +### 符号类型 + +在位置及符号中间的字符(以空格分开)是符号的*类型*。[UNIX操作系统](https://zh.m.wikipedia.org/wiki/UNIX操作系统)上的`nm`工具程序列出了所有目标文件的符号。而`System.map`则是直接与其相关,因为这个文件是在整个核心程序上由`nm`所产生的 - 就像`nm`列出任意小的目标程序的符号及它们的类型[[4\]](https://zh.m.wikipedia.org/zh-my/System.map#cite_note-nm-man-4)。 + +System.map文件格式:地址 类型 符号 + +类型是小写表示local symbol,大写表示global(external) + +这些类型的一部分:[[4\]](https://zh.m.wikipedia.org/zh-my/System.map#cite_note-nm-man-4) + +- `A` 绝对的 +- `B` 或 `b` 未初始化的资料段(称为BSS) +- `D` 或 `d` 已初始化的资料段 +- `G` 或 `g` 小目标的已初始化资料段(全局) +- `i` 特定的DLL段 +- `N` 调试符号 +- `p` 堆栈展开段 +- `R` 或 `r` 只读资料段 +- `S` 或 `s` 小目标的未初始化资料段 +- `T` 或 `t` 文字(代码)段 +- `U` 未定义 +- `V` 或 `v` 弱目标 +- `W` 或 `w` 尚未被标记的弱目标 +- `-` a.out目标文件的符号戳 +- `?` “符号类型未知” + + + +## /proc/kallsyms + + + +![image-20201127135310932](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201127135310932.png) + + + +> /proc/kallsysms have symbols of dynamically loaded modules as well static code and system.map is symbol tables of only static code. kallsyms包含了`kernel image`和动态加载模块的符号表,函数如果被编译器内联(inline)或优化掉,则它在/proc/kallsyms有可能找不到。 + + + +正在运行的内核可能和System.map不匹配,出现`System.map does not match actual kernel`,所以/proc/kallsyms才是参考的主要来源,我们应该通过/proc/kallsyms获得符号的地址。 + +/proc/kallsyms的形成过程为: + +- [/scripts/kallsyms.c](https://github.com/torvalds/linux/blob/master/scripts/kallsyms.c) 生成System.map +- [/kernel/kallsyms.c](https://github.com/torvalds/linux/blob/master/kernel/kallsyms.c) 生成/proc/kallsyms +- [/scripts/kallsyms.c](https://github.com/torvalds/linux/blob/master/scripts/kallsyms.c) 解析vmlinux(.tmp_vmlinux)生成kallsyms.S(.tmp_kallsyms.S),然后内核编译过程中将kallsyms.S(内核符号表)编入内核镜像uImage +- 内核启动后./kernel/kallsyms.c解析uImage形成/proc/kallsyms +- /proc/kallsyms包含了内核中的函数符号(包括没有EXPORT_SYMBOL)、全局变量(用EXPORT_SYMBOL导出的全局变量) + + + + + +## Module.symvers + +![image-20201127135217243](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201127135217243.png) + +**In a nutshell Module.symvers contains a list of all exported symbols from a kernel build.** + +During a kernel build the symvers or symbol versions file will be generated. If you pop it open, the general syntax of the file is : + +``` + +0x2d036834 scsi_remove_host drivers/scsi/scsi_mod +``` + +If “**CONFIG_MODVERSIONS”** is disabled then the **CRC** would contain an entry **0x00000000**. + +Modules.symvers serves two purposes: + +1. It lists all exported symbols from **vmlinux** and all **modules**. +2. It lists the **CRC** if **CONFIG_MODVERSIONS** is enabled. + +When building an external module, the build system needs access to the symbols from the kernel to check if all external symbols are defined. This is done in the **MODPOST** step. **modpost** obtains the symbols by reading **Module.symvers** from the kernel source tree. If a **Module.symvers** file is present in the directory where the external module is being built, this file will be read too. During the MODPOST step, **a new Module.symvers file will be written containing all exported symbols that were not defined in the kernel.** + +For further information : [/kbuild/modules.txt](https://www.kernel.org/doc/Documentation/kbuild/modules.txt) , in the linux kernel documentation. +``` + +`Linux/Linux开发/kernel/内核调试/内核崩溃调试.md`: + +```md +# 内核崩溃调试 + +环境:CentOS7.8 + + + + + +## 安装crash + +```sh +yum install crash +yum install kernel-devel +yum install kernel-debuginfo +yum install kernel-debuginfo-common +``` + + + +开启内核crash + +修改系统启动参数,通常是 grub.cfg 文件,在启动参数中添加如下: + +```shell +crashkernel=auto +``` + + + +## 编译调试版本驱动 + +```sh +EXTRA_CFLAGS=-g +``` + + + + + +## 调试命令 + +默认崩溃生成的vmcore在目录:`/var/crash/` + +```sh +[root@CentOS7 127.0.0.1-2020-12-07-07:37:28]# crash /usr/lib/debug/lib/modules/3.10.0-1127.el7.x86_64/vmlinux vmcore +``` + + + +![image-20201208100737934](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201208100737934.png) + + + +## bt -- 查看堆栈 + +bt 命令用于查看系统崩溃前的堆栈等信息,这是系统调试中非常常用和好用的一个命令。 + +用来进程调用回溯。 + +![image-20201208102042986](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201208102042986.png) + + + + + +## log -- dump system message buffer + +log命令可以打印系统消息缓冲区,从而可能找到系统崩溃的线索。主要是用来看自己的驱动打印信息。 + +![image-20201208102445056](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201208102445056.png) + + + + +``` + +`Linux/Linux开发/kernel/内核调试/内核调试所需.md`: + +```md + + +# 内核调试所需 + + + +## 调试符号 + +> 调试符号一般在源中找不到,需要在官网去找对应的版本 + +1. kernel-debuginfo + + ![image-20201128145038211](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201128145038211.png) + +2. kernel-debuginfo-common + +![image-20201128145110076](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201128145110076.png) + + + +## 设置被调试机 + + + +环境:CentOS7 + +```shell +vim /boot/grub2/grub.cfg +``` + + + +![image-20201128150726724](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201128150726724.png) + + + +kgdboc=ttyS0,115200 设置串口 + + + +nokaslr 禁用基址随机化 + + + +kgdbwait 开启直接等待 + + + +![image-20201128150908892](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201128150908892.png) + +添加串行端口,使用命名管道: + +//./pipe/com_1 + + + +该端是客户端 + +另一端是虚拟机 + + + +## 设置主机 + + + +```shell +vim /boot/grub2/grub.cfg +``` + +![image-20201128152144204](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201128152144204.png) + + + +![image-20201128151057937](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201128151057937.png) + + + + + +添加串行端口,使用命名管道: + +//./pipe/com_1 + + + +该端是服务端 + +另一端是虚拟机 + + + + + +## 开始调试 + + + +### 启动被调试机 + + + +被调试机开机后会被卡在这里: + + + +![image-20201128151306867](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201128151306867.png) + + + +### 主机调试 + +1. 开启gdb + +主机切换到`vmlinux`所在目录 + +![image-20201128151509817](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201128151509817.png) + + + +使用命令: + +```shell +gdb -tui --baud 15200 vmlinux +``` + +![image-20201128151716357](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201128151716357.png) + +2. 设置调试目标 + +设置目标: + +```shell +target remote /dev/ttyS0 +``` + + + +![image-20201128152621739](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201128152621739.png) + + + +之后就可以正常调试 了。 + + + +### 中断 + +当被调试机运行起来后,想要再次进入调试状态时,需要在被调试机输入以下命令: + +```shell +echo g > /proc/sysrq-trigger +``` + +主机gdb便会进入中断。 + + + +中断不生效,这是因为sysrq在系统未开启: + +```c +echo 1 > /proc/sys/kernel/sysrq +``` + +使用上面命令开启 + + +``` + +`Linux/Linux开发/kernel/内核调试/利用google syzkaller create-image.sh创建根文件系统.md`: + +```md +# 利用google / syzkaller create-image.sh创建根文件系统 + +## 下载create-image.sh + +修改后的create-image.sh + +[create-image.sh原始链接](https://raw.githubusercontent.com/google/syzkaller/master/tools/create-image.sh) + + + +```sh +#!/bin/bash +# Copyright 2016 syzkaller project authors. All rights reserved. +# Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +# create-image.sh creates a minimal Debian Linux image suitable for syzkaller. + +set -eux + +# Create a minimal Debian distribution in a directory. +DIR=chroot +PREINSTALL_PKGS=openssh-server,curl,tar,gcc,libc6-dev,time,strace,sudo,less,psmisc,selinux-utils,policycoreutils,checkpolicy,selinux-policy-default,firmware-atheros + +# If ADD_PACKAGE is not defined as an external environment variable, use our default packages +if [ -z ${ADD_PACKAGE+x} ]; then +# 修改下面这个位置,把想装的软件添加到下面 + ADD_PACKAGE="make,sysbench,git,vim,tmux,usbutils,tcpdump,ecryptfs-utils" +fi + +``` + + + +修改下载源: + +将地址修改为国内,方便快速下载 + +```shell +# 1. debootstrap stage + +DEBOOTSTRAP_PARAMS="--include=$PREINSTALL_PKGS --components=main,contrib,non-free $RELEASE $DIR http://ftp.cn.debian.org/debian/" +if [ $ARCH != $(uname -m) ]; then + DEBOOTSTRAP_PARAMS="--arch=$DEBARCH --foreign $DEBOOTSTRAP_PARAMS" +fi +sudo debootstrap $DEBOOTSTRAP_PARAMS +``` + + + +此脚本依赖`debootstrap` + +```sh +apt-get install debootstrap +``` + + + + + +```sh +sh create-image.sh --feature full +``` + + + +## 加载内核启动 + +```sh +#! /bin/sh +qemu-system-x86_64 -kernel /root/bionic/arch/x86/boot/bzImage -drive file=/root/stretch.img -append "root=/dev/sda nokaslr" -m 4096 -s -S +``` + + +``` + +`Linux/Linux开发/kernel/字符设备/Linux字符设备驱动.md`: + +```md +## 怎样获取字符设备动态申请的设备号 + + +```shell +[root@localhost module_cdev]# cat /proc/devices | grep "module" +247 module_cdev +``` + + +## 生成一个设备节点与设备关联 + +```shell +[root@localhost module_cdev]# cat /proc/devices | grep "module" +247 module_cdev +[root@localhost module_cdev]# man mknod +Formatting page, please wait... +[root@localhost module_cdev]# +[root@localhost module_cdev]# mknod cdev_node c 247 0 +[root@localhost module_cdev]# ls +cdev_node module_cdev.c module_cdev.mod.c modules.order +Makefile module_cdev.ko module_cdev.mod.o Module.symvers +Makefile~ module_cdev.ko.unsigned module_cdev.o +``` + + +``` + +`Linux/Linux开发/kernel/字符设备/自动创建设备节点.md`: + +```md +# 字符设备自动生成设备节点 + + + +## udev机制 + +**udev** 是Linux kernel 2.6系列的设备管理器。它主要的功能是管理`/dev`目录底下的[设备节点](https://zh.wikipedia.org/w/index.php?title=设备节点&action=edit&redlink=1)。它同时也是用来接替[devfs](https://zh.wikipedia.org/wiki/Devfs)及[hotplug](https://zh.wikipedia.org/w/index.php?title=Hotplug&action=edit&redlink=1)的功能,这意味着它要在添加/删除硬件时处理`/dev`目录以及所有用户空间的行为,包括加载[firmware](https://zh.wikipedia.org/wiki/Firmware)时。 + +udev的最新版本依赖于升级后的Linux kernel 2.6.13的[uevent](https://zh.wikipedia.org/w/index.php?title=Uevent&action=edit&redlink=1)接口的最新版本。使用新版本udev的系统不能在2.6.13以下版本启动,除非使用*noudev*参数来禁用udev并使用传统的`/dev`来进行设备读取。 + +2012年4月,udev被合并至[systemd](https://zh.wikipedia.org/wiki/Systemd)。 + + + +udev是一个通用的内核设备管理器。它以守护进程的方式运行于Linux系统,并监听在新设备初始化或设备从系统中移除时,内核(通过[netlink](https://zh.wikipedia.org/wiki/Netlink) socket)所发出的uevent。 + +系统提供了一套规则用于匹配可发现的设备事件和属性的导出值。匹配规则可能命名并创建设备节点,并运行配置程序来对设备进行设置。udev规则可以匹配像内核子系统、内核设备名称、设备的物理等属性,或设备序列号的属性。规则也可以请求外部程序提供信息来命名设备,或指定一个永远一样的自定义名称来命名设备,而不管设备什么时候被系统发现。 + + + +## 代码 + +[完整代码路径](https://github.com/supermanc88/linux-device-driver/tree/master/module_cdev) + +```c +struct class *module_class; +struct device *module_class_device; + + +static int __init module_cdev_init(void) +{ + int ret; + printk(KERN_INFO "module_cdev_init\n"); + + ret = alloc_chrdev_region(&dev_num, 0, 1, "module_cdev"); + + if(ret){ + printk(KERN_INFO "alloc chrdev region failed\n"); + goto error; + } + + my_cdev = cdev_alloc(); + + if(my_cdev == NULL){ + printk(KERN_INFO "cdev alloc failde\n"); + ret = -ENOMEM; + goto error1; + } + + cdev_init(my_cdev, &fops); + + ret = cdev_add(my_cdev, dev_num, 1); + + if(ret){ + printk(KERN_INFO "cdev add failed\n"); + goto error1; + } + + + // 在这里自动创建设备节点 + module_class = class_create(THIS_MODULE, "module_cdev_class"); + module_class_device = device_create(module_class, NULL, dev_num, NULL, "module_cdev_name"); + + + return 0; + + error1: + kfree(my_cdev); + unregister_chrdev_region(dev_num, 1); + + error: + return ret; +} + +static void __exit module_cdev_exit(void) +{ + kfree(my_cdev); + unregister_chrdev_region(dev_num, 1); + + device_destroy(module_class, dev_num); + class_destroy(module_class); + + printk(KERN_INFO "module_cdev_exit\n"); +} +``` + + + +在insmod后,`/dev/`目录下会出现名为`module_cdev_name`的设备 + + + +![image-20201208194854479](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201208194854479.png) + + + +在`/sys/class/`目录下出现了`module_cdev_name`的类 + + + +![image-20201208194925120](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201208194925120.png) + + + +在rmmod后,以上的目录均消失。 +``` + +`Linux/Linux开发/kernel/开发环境搭建.md`: + +```md +# 驱动开发环境 + +需要安装和当前内核版本号一致的: + + + +## 安装所需头文件及相关源码 + +```shell +kernel-devel // release版驱动开发头文件 +kernel-debuginfo // release版内核调试符号 +kernel-debuginfo-common // 内核源码 + + +kernel-debug // debug版内核 +kernel-debug-devel // debug版驱动开发头文件 +kernel-debug-debuginfo // debug版内核调试符号 +``` + + + +## 安装编译环境 + +```shell +yum install gcc make +``` + + + + + +## 安装驱动源码 + +可能会出现下面的错误: + +```shell +[root@localhost ~]# rpm -iv kernel-3.10.0-693.el7.src.rpm +kernel-3.10.0-693.el7.i686 +warning: user mockbuild does not exist - using root +warning: group mockbuild does not exist - using root +warning: user mockbuild does not exist - using root +warning: group mockbuild does not exist - using root +``` + + + + + +解决办法: + +```shell +[root@localhost ~]# groupadd mockbuild +[root@localhost ~]# useradd mockbuild -g mockbuild +``` + + +``` + +`Linux/Linux开发/kernel/驱动签名.md`: + +```md +# 驱动签名 + +## 配置 + +自 3.7 内核之后有了内核签名机制。 + +编译内核时 .config 中有跟内核签名相关的选项有如下: + +```shell +CONFIG_MODULE_SIG=y +# CONFIG_MODULE_SIG_FORCE is not set +CONFIG_MODULE_SIG_ALL=y +CONFIG_MODULE_SIG_UEFI=y +# CONFIG_MODULE_SIG_SHA1 is not set +# CONFIG_MODULE_SIG_SHA224 is not set +CONFIG_MODULE_SIG_SHA256=y +# CONFIG_MODULE_SIG_SHA384 is not set +# CONFIG_MODULE_SIG_SHA512 is not set +CONFIG_MODULE_SIG_HASH="sha256" +``` + + + +## 错误 + +如果没有签名的话,安装驱动时提示: + +```shell +Dec 31 11:20:22 localhost kernel: infosecfs: loading out-of-tree module taints kernel. +Dec 31 11:20:22 localhost kernel: infosecfs: module verification failed: signature and/or required key missing - tainting kernel +``` + + + +## 解决 + +[Linux内核模块签名 - 越安静,听见的越多 (ihexon.github.io)](https://ihexon.github.io/develop/Signed-kernel-module-support/) + +[Linux kernel4.0 内核环境搭建,签名问题解决 | BasicBit](https://basicbit.cn/2018/10/16/2018-10-16-编译Linux内核4.17/#查看驱动输出) +``` + +`Linux/Linux开发/manpage如何使用.md`: + +```md +# manpage如何使用 + + + +章节号 + +| 章节号 | 含义 | +| ------ | ------------------------------------------- | +| 1 | 用户在shell环境中可以实现的命令或可执行文件 | +| 2 | 系统内核可调用的函数和工具 | +| 3 | 一些常见的函数与函数库,大部分为C的函数库 | +| 4 | 设备文件的说明,通常放在/dev下的文件 | +| 5 | 配置文件或者某些文件的格式 | +| 6 | 游戏 | +| 7 | 惯例与协议,例如Linux文件系统、网络协议等 | +| 8 | 系统管理员可使用的管理命令 | +| 9 | 跟kernel有关的文件 | +| 1P | 开发者章节 | + + + +详细介绍: + +https://en.wikipedia.org/wiki/Man_page +``` + +`Linux/Linux开发/open系统调用.md`: + +```md +注意: 头文件 unistd 必须在首行 + +在调用open函数时,在使用`O_CREAT`时,要往里面写内容时,仅使用`O_RDONLY`参数不能写入,在替换`O_RDWR`后,可写入。 + +``` + +`Linux/Linux开发/setuid与setgid讲解.md`: + +```md +# setuid与setgid讲解 + +文件权限的机制是Linux系统中的一大特色,除了熟知的读(r)、写(w)、执行(x)权限外,还有三个比较特殊的权限,分别为:`setuid`、`setgid`和`stick bit`(粘滞位) + + + +## setuid与setgid + +看一下系统中用到它的地方,以/etc/passwd和/usr/bin/passwd为例: + +```shell +[root@Salve1 school]# ll /etc/passwd /usr/bin/passwd +-rw-r--r-- 1 root root 2005 Apr 23 01:25 /etc/passwd +-rwsr-xr-x 1 root root 23420 Aug 11 2010 /usr/bin/passwd +``` + +分析一下,/etc/passwd的权限为 `-rw-r--r--` 也就是说: + +该文件的所有者拥有读写的权限,而用户组成员和其它成员只有查看的权限。 + +我们知道,在系统中我们要修改一个用户的密码, + +`root`用户和普通用户均可以用`/usr/bin/passwd someuser`这个命令来修改这个`/etc/passwd`这个文件 + +`root`用户本身拥有对`/etc/passwd`的写权限,无可厚非; + +那普通用户呢,这里就用到了`setuid`,**`setuid`的作用是“让执行该命令的用户以该命令拥有者的权限去执行”** + +就是普通用户执行`passwd`时会拥有`root`的权限,这样就可以修改`/etc/passwd`这个文件了。 + +它的标志为:`s`,会出现在x的地方,例:`-rwsr-xr-x` 。 + +而`setgid`的意思和它是一样的,即**让执行文件的用户以该文件所属组的权限去执行。** + + + +## stick bit(粘滞位) + +看一下系统中用到它的地方,以/tmp为例: + +```shell +[root@Salve1 /]# ll -d /tmp +drwxrwxrwt 13 root root 4096 Apr 23 02:06 /tmp +``` + +我们知道/tmp是系统的临时文件目录,所有的用户在该目录下拥有所有的权限, + +也就是说在该目录下可以任意创建、修改、删除文件,那如果用户A在该目录下创建了一个文件 + +如果用户B将该文件删除了,这种情况我们是不能允许的。为了达到该目的,就出现了stick bit(粘滞位)的概念。 + +它是针对目录来说的,如果该目录设置了stick bit(粘滞位) + +则该目录下的文件除了该文件的创建者和root用户可以删除和修改/tmp目录下的stuff + +别的用户均不能动别人的,这就是粘滞位的作用。 + + + +## 如何设置上述权限 + +```shell +# 设置setuid权限 +chmod u+s xxx + +# 设置setgid权限 +chmod g+s xxx + +# 设置stick bit权限,针对目录 +chmod o+t xxx + +# 设置setuid权限 +chmod 4775 xxx + +# 设置setgid权限 +chmod 2775 xxx + +# 设置stick bit权限,针对目录 +chmod 1775 xxx +``` + +> 注意:有时你设置了s或t 权限,你会发现它变成了S或T, +> +> 这是因为在那个位置上你没有给它x(可执行)的权限,这样的话这样的设置是不会有效的, +> +> 你可以先给它赋上x的权限,然后再给s或t 的权限。 +``` + +`Linux/Linux开发/中标麒麟解压vmlinuz.md`: + +```md +**这样做的原因主要是中标麒麟不能调试内核** + + +## vmlinux,vmlinuz,bzImage,zImage + +### vmlinux + +vmlinux是未压缩的内核,是make工作编译出的原始内核,vmlinuz是vmlinux的压缩文件。 + +vmlinux 是ELF文件,即编译出来的最原始的文件。 + +### zImage, bzImage和vmlinuz + +vmlinuz是可引导的、压缩的内核。“vm”代表“Virtual Memory”。Linux 支持虚拟内存,不像老的操作系统比如DOS有640KB内存的限制。Linux能够使用硬盘空间作为虚拟内存,因此得名“vm”。vmlinuz是可执行的Linux内核,它位于/boot/vmlinuz,它一般是一个软链接,是bzImage/zImage文件的拷贝或指向bzImage/zImage的链接。 + +vmlinuz的建立有两种方式。 + +- 一是编译内核时通过“make zImage”创建,然后通过:“cp /usr/src/linux-2.4/arch/i386/linux/boot/zImage /boot/vmlinuz”产生。zImage适用于小内核的情况,它的存在是为了向后的兼容性。 + +- 二是内核编译时通过命令make bzImage创建,然后通过:“cp /usr/src/linux-2.4/arch/i386/linux/boot/bzImage /boot/vmlinuz”产生。bzImage是压缩的内核映像,需要注意,bzImage不是用bzip2压缩的,bzImage中的bz容易引起误解,bz表示“big zImage”。 bzImage中的b是“big”意思。 + +zImage(vmlinuz)和bzImage(vmlinuz)都是用gzip压缩的。它们不仅是一个压缩文件,而且在这两个文件的开头部分内嵌有gzip解压缩代码。所以你不能用gunzip 或 gzip –dc解包vmlinuz。 + +内核文件中包含一个微型的gzip用于解压缩内核并引导它。两者的不同之处在于,老的zImage解压缩内核到低端内存(第一个640K),bzImage解压缩内核到高端内存(1M以上)。如果内核比较小,那么可以采用zImage 或bzImage之一,两种方式引导的系统运行时是相同的。大的内核采用bzImage,不能采用zImage。 + +> 但是注意通常情况下是不能用vmlinuz解压缩得到vmlinux的 + + +## 解压vmlinuz + +``` +[root@localhost boot]# file vmlinuz-2.6.32-696.el6.x86_64 +vmlinuz-2.6.32-696.el6.x86_64: Linux kernel x86 boot executable bzImage, version 2.6.32-696.el6.x86_64 (mockbuil, RO-rootFS, root_dev 0x803, swap_dev 0x4, Normal VGA +[root@localhost boot]# od -t x1 -A d vmlinuz-2.6.32-696.el6.x86_64 | grep "1f 8b 08" +0014432 48 8d 83 b0 cf 40 00 ff e0 1f 8b 08 00 be 32 26 +2934512 d8 7e 8c a6 3b 7a 1f 8b 08 1b 95 1b e2 1f b0 0b +[root@localhost boot]# dd if=vmlinuz-2.6.32-696.el6.x86_64 bs=1 skip=14441 | zcat > vmlinux + +gzip: stdin: decompression OK, trailing garbage ignored + +[root@localhost boot]# file vmlinux +vmlinux: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped + +``` + + +参考: + +http://smilejay.com/2013/06/extract-vmlinuz-and-initrd/ + +https://www.binss.me/blog/boot-process-of-linux-decompress-kernel/ + +https://www.cnblogs.com/alantu2018/p/8991298.html + +``` + +`Linux/Linux配置/A stop job.md`: + +```md +**一、现象** + +Linux关机时提示A stop job is running for .. + +导致关机慢。 + +**二、解决方法** + +编辑: + +```sh +/etc/systemd/system.conf +``` + +修改下面两个变量为: + +```sh +DefaultTimeoutStartSec=10s +DefaultTimeoutStopSec=10s +``` + +执行: + +```sh +systemctl daemon-reload +``` +``` + +`Linux/Linux配置/Linux程序版本管理--update-alternatives.md`: + +```md +# Linux程序版本管理 + +使用`update-alternatives`, 其实就是一个符号链接管理工具 + +```shell +NAME + alternatives - maintain symbolic links determining default commands + +SYNOPSIS + alternatives [options] --install link name path priority [--slave link name path]... + [--initscript service] + + alternatives [options] --remove name path + + alternatives [options] --set name path + + alternatives [options] --auto name + + alternatives [options] --display name + + alternatives [options] --config name + +``` + +例子: + +```shell +update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 40 --slave /usr/bin/g++ g++ /usr/bin/g++-4.8 + +update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 50 --slave /usr/bin/g++ g++ /usr/bin/g++-5 + +update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 70 --slave /usr/bin/g++ g++ /usr/bin/g++-7 +``` + + + +选择要使用的版本: + +```shell +[root@localhost ~]# update-alternatives --config gcc + +There are 2 programs which provide 'gcc'. + + Selection Command +----------------------------------------------- + 1 /usr/bin/gcc-4.1.2 +*+ 2 /usr/local/bin/gcc + +Enter to keep the current selection[+], or type selection number: +``` + + + +删除: + +```shell +update-alternatives --remove gcc /usr/bin/gcc-4.1.2 +``` + + +``` + +`Linux/Linux配置/Linux虚拟机移动后网卡配置.md`: + +```md +# 解决centos网卡启动device eth0 does not seem to be present, delaying initialization + + + +启动系统后不能上网,通过ifconfig查看网卡没有eth0,重启网卡服务,但是显示出错,第三行显示:device eth0 does not seem to be present, delaying initialization. 然后想到是不是ifcfg-eth0的配置文件里保存了以前的MAC地址,就把这一行删除掉在重启网卡,还是一样的错误,随后网上查了下资料,把/etc/udev/rules.d/70-persistent-net.rules 删除后重启机器就可以了,因为这个文件绑定了网卡和mac地址,所以换了网卡以后MAC地址变了,所以不能正常启动,也可以直接编辑这个配置文件把里面的网卡和mac地址修改乘对应的,不过这样多麻烦,直接删除重启,它会自动生成个。 + + + +![wKiom1T-XYbi-EcsAAB9gymKFEg125](https://raw.githubusercontent.com/supermanc88/ImageSources/master/wKiom1T-XYbi-EcsAAB9gymKFEg125.jpg) + + + +```shell +vi /etc/sysconfig/network-scripts/ifcfg-eth0 + +rm /etc/udev/rules.d/70-persistent-net.rules +``` + + + + +``` + +`Linux/Linux配置/linux代理.md`: + +```md +# Linux代理 + +在`/etc/profile.d`文件夹下创建文件`proxy.sh` + + + +添加如下内容: + +```shell +export proxy="http://192.168.45.1:7890" +export http_proxy=$proxy +export https_proxy=$proxy +export ftp_proxy=$proxy +export no_proxy="localhost, 127.0.0.1, ::1" +``` + + + +reboot或 `source /etc/profile.d/proxy.sh`生效 + + + + +``` + +`Linux/Linux配置/linux压缩.md`: + +```md +# linux 压缩 + + + +## tar.gz + +```shell +tar -zxvf xx.tar.gz +``` + + + +## tar.xz + +```shell +tar -xvf xxx.tar.xz +``` + + + + + +## tar.bz2 + +```shell +tar -jxvf xx.tar.bz2 +``` + + + +## zip + +```shell +unzip xx.zip +``` + + + +## cpio + +```shell +// 会解压到当前目录 +cpio -idv < 文件名.cpio +``` + + +``` + +`Linux/Linux配置/linux防火墙.md`: + +```md +# 防火墙 + +## 关闭防火墙 + +CentOS7 + +```shell +service firewalld stop +``` + +CentOS6.10 + +```shell +service iptables stop +``` + + + + + + +``` + +`Linux/Linux配置/ubuntu18默认命令行模式.md`: + +```md +# ubuntu18.04开机默认进入命令行模式/用户图形界面 + +一、开机默认进入命令行模式 + 1、输入命令:`sudo systemctl set-default multi-user.target` + 2、重启:reboot + 要进入图形界面,只需要输入命令`sudo startx` + 从图形界面切换回命令行:ctrl+alt+F7 + +二、开机默认进入图形用户界面 + 1、输入命令:`sudo systemctl set-default graphical.target` + 2、重启:reboot + 要进入命令行模式:ctrl+alt+F2 + 从命令行切换到图形界面:ctrl+alt+F7 +``` + +`Linux/Linux配置/yum命令.md`: + +```md +# yum命令 + + + +yum clean all + +yum makecache + + + +yum remove packages + + + +## 查看安装的软件 + +```shell +// 列出所有已安装的软件包信息 +yum info installed + +yum list +``` + + + +## 查找软件包 + +```shell +yum search +``` + + + +## 删除软件包命令 + +```shell +yum remove +``` + + +``` + +`Linux/Shell/Linux Shell.md`: + +```md +# Linux Shell + + + +## Shell 中的冒号 + +在Linux系统中,冒号(:)常用来做路径的分隔符(PATH),数据字段的分隔符(/etc/passwd)等。其实,冒号(:)在Bash中也是一个内建命令,它啥也不做,是个空命令、只起到占一个位置的作用,但有时候确实需要它。当然,它也有它的用途的,否则没必要存在。在·Linux的帮助页中说它除了参数扩展和重定向之外不产生任何作用。 + + + +### 常用格式 + +- `:` + + 啥也不做,只起到占位符的作用。比如在编写脚本的过程中,某些语法结构需要多个部分组成,但开始阶段并没有想好或完成相应的代码,这时就可以用:来做占位符,否则执行时就会报错。 + + ```shell + if [ "today" == "2011-08-29" ]; then + : + else + : + fi + ``` + +- `: your comment here` + + \# your comment here + + 写代码注释(单行注释)。 + +- 多行注释 + + ```shell + : 'comment line1 + + comment line2 + + more comments' + ``` + +- `: >file` + + 清空文件file的内容。 + +- `: ${VAR:=DEFAULT}` + + 当变量VAR没有声明或者为NULL时,将VAR设置为默认值DEFAULT。如果不在前面加上:命令,那么就会把${VAR:=DEFAULT}本身当做一个命令来执行,报错是肯定的。 + + + +### 示例 + +示例一 参数扩展 + +```shell +[root@node56 ~]# : abc=1234 +[root@node56 ~]# echo $abc + +[root@node56 ~]# : ${abc:=1234} +[root@node56 ~]# echo $abc +1234 + +[root@node56 ~]# ${abc:=1234} +-bash: 1234: command not found +[root@node56 ~]# +``` + + + + + +示例二 清空文件 + +```shell +[root@node56 ~]# cat <<<"Hello" >123.txt +[root@node56 ~]# cat 123.txt +Hello +[root@node56 ~]# : >123.txt +[root@node56 ~]# cat 123.txt +[root@node56 ~]# +``` + + + +示例三 脚本注释、占位符 + +脚本test_colon.sh + +```shell +#!/bin/sh + +: this is single line comment + +: 'this is a multiline comment, +second line +end of comments' + +if [ "1" == "1" ]; then + echo "yes" +else + : +fi +``` + +[root@node56 ~]# ./test_colon.sh +yes + + + +## sed + +sed编辑器被称作`流编辑器(steam editor)`, + + + + + +## Shell 流程控制 + +### if else + +- if + +```shell +if condition +then + command1 + command2 + ... + commandN +fi +``` + +- if else + +```shell +if condition +then + command1 + command2 + ... + commandN +else + command +fi +``` + +- if else-if else + +```shell +if condition1 +then + command1 +elif condition2 +then + command2 +else + commandN +fi +``` + +#### 1 字符串判断 + +| 条件表达式 | 含义 | +| ------------ | ------------------------------ | +| str1 = str2 | 当两个串有相同内容、长度时为真 | +| str1 != str2 | 当串str1和str2不等时为真 | +| -n str1 | 当串的长度大于0时为真(串非空) | +| -z str1 | 当串的长度为0时为真(空串) | +| str1 | 当串str1为非空时为真 | + +#### 2 数字的判断 + +| 条件表达式 | 含义 | +| ------------- | -------------------- | +| int1 -eq int2 | 两数相等为真 | +| int1 -ne int2 | 两数不等为真 | +| int1 -gt int2 | int1大于int2为真 | +| int1 -ge int2 | int1大于等于int2为真 | +| int1 -lt int2 | int1小于int2为真 | +| int1 -le int2 | int1小于等于int2为真 | + +#### 3 文件的判断 + +| 条件表达式 | 含义 | +| ---------- | ------------------------------------------- | +| -r file | 用户可读为真 | +| -w file | 用户可写为真 | +| -x file | 用户可执行为真 | +| -f file | 文件为正规文件为真 | +| -d file | 文件为目录为真 | +| -c file | 文件为字符特殊文件为真 | +| -b file | 文件为块特殊文件为真 | +| -s file | 文件大小非0时为真 | +| -t file | 当文件描述符(默认为1)指定的设备为终端时为真 | + +#### 4 复杂逻辑判断 + +| 条件表达式 | 含义 | +| ---------- | ---- | +| -a | 与 | +| -o | 或 | +| ! | 非 | +``` + +`Linux/Shell/Linux重定向.md`: + +```md +# Linux重定向 + +| 命令 | 说明 | +| :-------------- | :------------------------------------------------- | +| command > file | 将输出重定向到 file。 | +| command < file | 将输入重定向到 file。 | +| command >> file | 将输出以**追加**的方式重定向到 file。 | +| n > file | 将文件描述符为 n 的文件重定向到 file。 | +| n >> file | 将文件描述符为 n 的文件以追加的方式重定向到 file。 | +| n >& m | 将输出文件 m 和 n 合并。 | +| n <& m | 将输入文件 m 和 n 合并。 | +| << tag | 将开始标记 tag 和结束标记 tag 之间的内容作为输入。 | + +*需要注意的是文件描述符 **0** 通常是**标准输入(STDIN)**,**1** 是**标准输出(STDOUT)**,**2** 是**标准错误输出(STDERR)**。* + + + +```shell +> + # 将stdout重定向到一个文件. + # 如果这个文件不存在, 那就创建, 否则就覆盖. + + ls -lR > dir-tree.list + # 创建一个包含目录树列表的文件. + +: > filename + # >操作, 将会把文件"filename"变为一个空文件(就是size为0). + # 如果文件不存在, 那么就创建一个0长度的文件(与'touch'的效果相同). + # :是一个占位符, 不产生任何输出. + +> filename + # >操作, 将会把文件"filename"变为一个空文件(就是size为0). + # 如果文件不存在, 那么就创建一个0长度的文件(与'touch'的效果相同). + # (与上边的": >"效果相同, 但是某些shell可能不支持这种形式.) + +>> + # 将stdout重定向到一个文件. + # 如果文件不存在, 那么就创建它, 如果存在, 那么就追加到文件后边. + + # 单行重定向命令(只会影响它们所在的行): + + +---------- + + +1>filename + # 重定向stdout到文件"filename". +1>>filename + # 重定向并追加stdout到文件"filename". +2>filename + # 重定向stderr到文件"filename". +2>>filename + # 重定向并追加stderr到文件"filename". +&>filename + # 将stdout和stderr都重定向到文件"filename". + +M>N + # "M"是一个文件描述符, 如果没有明确指定的话默认为1. + # "N"是一个文件名. + # 文件描述符"M"被重定向到文件"N". +M>&N + # "M"是一个文件描述符, 如果没有明确指定的话默认为1. + # "N"是另一个文件描述符. + + +---------- + + + # 重定向stdout, 一次一行. + LOGFILE=script.log + + echo "This statement is sent to the log file, \"$LOGFILE\"." 1>$LOGFILE + echo "This statement is appended to \"$LOGFILE\"." 1>>$LOGFILE + echo "This statement is also appended to \"$LOGFILE\"." 1>>$LOGFILE + echo "This statement is echoed to stdout, and will not appear in \"$LOGFILE\"." + # 每行过后, 这些重定向命令会自动"reset". + + + + # 重定向stderr, 一次一行. + ERRORFILE=script.errors + + bad_command1 2>$ERRORFILE # Error message sent to $ERRORFILE. + bad_command2 2>>$ERRORFILE # Error message appended to $ERRORFILE. + bad_command3 # Error message echoed to stderr, + #+ and does not appear in $ERRORFILE. + # 每行过后, 这些重定向命令也会自动"reset". + +---------- + + +2>&1 + # 重定向stderr到stdout. + # 将错误消息的输出, 发送到与标准输出所指向的地方. + +i>&j + # 重定向文件描述符i到j. + # 指向i文件的所有输出都发送到j. + +>&j + # 默认的, 重定向文件描述符1(stdout)到j. + # 所有传递到stdout的输出都送到j中去. + +0< FILENAME + < FILENAME + # 从文件中接受输入. + # 与">"是成对命令, 并且通常都是结合使用. + # + # grep search-word filename + # 为了读写"filename", 把文件"filename"打开, 并且将文件描述符"j"分配给它. + # 如果文件"filename"不存在, 那么就创建它. + # 如果文件描述符"j"没指定, 那默认是fd 0, stdin. + # + # 这种应用通常是为了写到一个文件中指定的地方. + echo 1234567890 > File # 写字符串到"File". + exec 3<> File # 打开"File"并且将fd 3分配给它. + read -n 4 <&3 # 只读取4个字符. + echo -n . >&3 # 写一个小数点. + exec 3>&- # 关闭fd 3. + cat File # ==> 1234.67890 + +| + # 管道. + # 通用目的处理和命令链工具. + # 与">", 很相似, 但是实际上更通用. + # 对于想将命令, 脚本, 文件和程序串连起来的时候很有用. + cat *.txt | sort | uniq > result-file + # 对所有.txt文件的输出进行排序, 并且删除重复行. + # 最后将结果保存到"result-file"中. + + + +---------- + + +command > filename      把标准输出重定向到一个新文件中 + +command >> filename      把标准输出重定向到一个文件中(追加) + +command 1 > fielname      把标准输出重定向到一个文件中 + +command > filename 2>&1    把标准输出和标准错误一起重定向到一个文件中 + +command 2 > filename     把标准错误重定向到一个文件中 + +command 2 >> filename     把标准输出重定向到一个文件中(追加) + +command >> filename 2>&1   把标准输出和标准错误一起重定向到一个文件中(追加) + +command < filename >filename2   把command命令以filename文件作为标准输入,以filename2文件作为标准输出 + +command < filename    把command命令以filename文件作为标准输入 + +command << delimiter   把从标准输入中读入,直至遇到delimiter分界符 + +command <&m    把文件描述符m作为标准输入 + +command >&m    把标准输出重定向到文件描述符m中 + +command <&-    把关闭标准输入 +———————————————— +``` + + +``` + +`Linux/Ubuntu安装rime输入法.md`: + +```md +# Ubuntu安装rime输入法 + +## 安装ibus + +``` +sudo apt-get install ibus-rime +``` + +## 安装Plum + +可通过Plum安装自己喜欢的输入法: + +``` +curl -fsSL https://git.io/rime-install | bash + +bash rime-install wubi pinyin-simp +``` + +## 配置rime + +![setting](https://raw.githubusercontent.com/supermanc88/ImageSources/master/2019-11-17_00-49-36.png) + +在`default.yaml`中添加上输入法 + +![setting](https://raw.githubusercontent.com/supermanc88/ImageSources/master/2019-11-17_00-52-01.png) + + +## 添加输入源 + +![inputsource](https://raw.githubusercontent.com/supermanc88/ImageSources/master/2019-11-17_00-37-06.png) + +重启操作系统就可以用了 + + +## 安装emoji + +### Linux 安装 + +使用Plum安装: + +``` +bash rime-install emoji +``` + +配置: + +``` +bash rime-install emoji:customize:schema=luna_pinyin +``` + +### Windows安装 + +![windowssetupemoji](https://raw.githubusercontent.com/supermanc88/ImageSources/master/1575944204(1).jpg) + +![windowssetupemoji1](https://raw.githubusercontent.com/supermanc88/ImageSources/master/1575944294(1).jpg) + +在windows上使用官方方法不容易成功,现使用手动方式: + +![manualsetup](https://raw.githubusercontent.com/supermanc88/ImageSources/master/1575950308(1).jpg) + +在安装emoji后,用户文件夹中会出现`emoji_suggestion.yaml`和`opencc`文件夹。 + +比如我要给五笔添加emoji,新建文件`wubi86.custom.yaml` + +将`emoji_suggestion.yaml`中的内容全部复制到`wubi86.custom.yaml`中,重新部署即可。 + + + +``` + +`Linux/网络开发/SIGPIPE信号.md`: + +```md +# 网络编程中的SIGPIPE信号 + +man 2 write 中有如下内容: + +> EPIPE fd is connected to a pipe or socket whose reading end is closed. When this happens the writing process will also receive a SIGPIPE signal. (Thus, the write return value is seen only if the program catches, blocks or ignores this signal.) + +产生原因:当一个进程向已经关闭的socket写数据时,便会产生此信号,进程会自动终止。 + + + +这种现象是很常见的,譬如说,当 client 连接到 server 之后,这时候 server 准备向 client 发送多条消息,但在发送消息之前,client 进程意外奔溃了,那么接下来 server 在发送多条消息的过程中,就会出现`SIGPIPE`信号。下面我们看看 server 的代码: + + + +```c +#include +#include +#include +#include +#include +#include +#include +#define MAXLINE 1024 +void handle_client(int fd) +{ + // 假设此时 client 奔溃, 那么 server 将接收到 client 发送的 FIN + sleep(5); + // 写入第一条消息 + char msg1[MAXLINE] = {"first message"}; + ssize_t n = write(fd, msg1, strlen(msg1)); + printf("write %ld bytes\n", n); + // 此时第一条消息发送成功,server 接收到 client 发送的 RST + sleep(1); + // 写入第二条消息,出现 SIGPIPE 信号,导致 server 被杀死 + char msg2[MAXLINE] = {"second message"}; + n = write(fd, msg2, strlen(msg2)); + printf("%ld, %s\n", n, strerror(errno)); +} +int main() +{ + unsigned short port = 8888; + struct sockaddr_in server_addr; + bzero(&server_addr, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_addr.s_addr = htonl(INADDR_ANY); + server_addr.sin_port = htons(port); + int listenfd = socket(AF_INET , SOCK_STREAM , 0); + bind(listenfd, (struct sockaddr *)&server_addr, sizeof(server_addr)); + listen(listenfd, 128); + int fd = accept(listenfd, NULL, NULL); + handle_client(fd); + return 0; +} +``` + + + +我们可以使用 Linux 的 nc 工具作为 client,当 client 连接到 server 之后,就立即杀死 client (模拟 client 的意外奔溃)。这时可以观察 server 的运行情况: + +```c +$ gcc -o server server.c +$ ./server & # 后台运行 server +$ nc localhost 8888 # 运行 nc 连接到 server +^C # Ctrl-C 杀死 nc +write 13 bytes +[1]+ Broken pipe ./server +``` + + + +  让我们分析一下整个过程: + +- client 连接到 server 之后,client 进程意外奔溃,这时它会发送一个 FIN 给 server。 +- 此时 server 并不知道 client 已经奔溃了,所以它会发送第一条消息给 client。但 client 已经退出了,所以 client 的 TCP 协议栈会发送一个 RST 给 server。 +- server 在接收到 RST 之后,继续写入第二条消息。往一个已经收到 RST 的 socket 继续写入数据,将导致`SIGPIPE`信号,从而杀死 server。 +- + +对 server 来说,为了不被`SIGPIPE`信号杀死,那就需要忽略`SIGPIPE`信号: + +```c +int main() +{ + signal(SIGPIPE, SIG_IGN); // 忽略 SIGPIPE 信号 + // ... +} +``` + + + +重新运行上面的程序,server 在发送第二条消息的时候,`write()`会返回`-1`,并且此时`errno`的值为`EPIPE`,所以这时并不会产生`SIGPIPE`信号: + +```c +char msg2[MAXLINE] = {"second message"}; +n = write(fd, msg2, strlen(msg2)); +printf("%ld, %s\n", n, strerror(errno)); // -1, Broken pipe +``` + + +``` + +`Linux/网络开发/TCPUDP端口0的作用.md`: + +```md +# TCP/UDP端口0的作用 + +TCP/UDP通信中使用的端口为0-65535,其中0-1023作为是Internet Assigned Numbers Authority(IANA)规定的保留端口。 + + + +不同于大多数的保留端口,0是作为禁止在TCP/UDP通信中直接使用的端口而定义的。 + + + +端口0在网络编程(尤其是Unix socket编程)中有非常重要的意义,作为动态端口,它委托操作系统来帮它找到一个实际可用的端口。 + + + + + +## 网络编程中端口0如何工作? + +我们知道一个socket连接需要连接双方都拥有各自的端口号,这样才能完成端对端的连接。IANA事先定义了一些基础的网络服务端口(比如HTTP:80,SSH:22),但对于大多数的自定义应用,需要我们在启动的时候来指定其端口。 + + + +通常我们调用`bind(port)`方法来向操作系统申请一个指定的端口,但有时会遇到该端口被占用的情况。为了应对这种硬编码带来的问题,当我们调用`bind(0)`来向系统申请绑定端口时,系统会从可用端口列表中获取一个并返回。 +``` + +`Linux/网络开发/codes/noblock_epoll/client.c`: + +```c +/* client.c */ +#include +#include +#include +#include +#include + +#define MAXLINE 10 +#define SERV_PORT 8000 + +int main(int argc, char *argv[]) +{ + struct sockaddr_in servaddr; + char buf[MAXLINE]; + int sockfd, i; + char ch = 'a'; + + sockfd = socket(AF_INET, SOCK_STREAM, 0); + + bzero(&servaddr, sizeof(servaddr)); + servaddr.sin_family = AF_INET; + inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); + servaddr.sin_port = htons(SERV_PORT); + + connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); + + while (1) { + //aaaa\n + for (i = 0; i < MAXLINE/2; i++) + buf[i] = ch; + buf[i-1] = '\n'; + ch++; + //bbbb\n + for (; i < MAXLINE; i++) + buf[i] = ch; + buf[i-1] = '\n'; + ch++; + //aaaa\nbbbb\n + write(sockfd, buf, sizeof(buf)); + sleep(10); + } + + close(sockfd); + + return 0; +} + + +``` + +`Linux/网络开发/codes/noblock_epoll/makefile`: + +``` +src = $(wildcard *.c) +targets = $(patsubst %.c, %, $(src)) + +CC = gcc +CFLAGS = -Wall -g + +all:$(targets) + +$(targets):%:%.c + $(CC) $< -o $@ $(CFLAGS) + +.PHONY:clean all +clean: + -rm -rf $(targets) + + +``` + +`Linux/网络开发/codes/noblock_epoll/server.c`: + +```c +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAXLINE 10 +#define SERV_PORT 8000 + +int main(void) +{ + struct sockaddr_in servaddr, cliaddr; + socklen_t cliaddr_len; + int listenfd, connfd; + char buf[MAXLINE]; + char str[INET_ADDRSTRLEN]; + int efd, flag; + + listenfd = socket(AF_INET, SOCK_STREAM, 0); + + bzero(&servaddr, sizeof(servaddr)); + servaddr.sin_family = AF_INET; + servaddr.sin_addr.s_addr = htonl(INADDR_ANY); + servaddr.sin_port = htons(SERV_PORT); + + bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); + + listen(listenfd, 20); + + /////////////////////////////////////////////////////////////////////// + struct epoll_event event; + struct epoll_event resevent[10]; + int res, len; + + efd = epoll_create(10); + + event.events = EPOLLIN | EPOLLET; /* ET 边沿触发,默认是水平触发 */ + + //event.events = EPOLLIN; + printf("Accepting connections ...\n"); + cliaddr_len = sizeof(cliaddr); + connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); + printf("received from %s at PORT %d\n", + inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), + ntohs(cliaddr.sin_port)); + + flag = fcntl(connfd, F_GETFL); /* 修改connfd为非阻塞读 */ + flag |= O_NONBLOCK; + fcntl(connfd, F_SETFL, flag); + + event.data.fd = connfd; + epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event); //将connfd加入监听红黑树 + while (1) { + printf("epoll_wait begin\n"); + res = epoll_wait(efd, resevent, 10, -1); //最多10个, 阻塞监听 + printf("epoll_wait end res %d\n", res); + + if (resevent[0].data.fd == connfd) { + while ((len = read(connfd, buf, MAXLINE/2)) >0 ) //非阻塞读, 轮询 + write(STDOUT_FILENO, buf, len); + } + } + + return 0; +} + + + +``` + +`Linux/网络开发/codes/tcp_udp_common_port/CMakeLists.txt`: + +```txt +cmake_minimum_required(VERSION 3.7) +project(tcp_udp_oneport) + +set(CMAKE_CXX_STANDARD 11) + +add_executable(tcp_udp_oneport main.cpp) +target_link_libraries(tcp_udp_oneport pthread) +``` + +`Linux/网络开发/codes/tcp_udp_common_port/main.cpp`: + +```cpp +#include +#include +#include +#include +#include +#include + + +void * udpthread(void *) { + int sfd; + struct sockaddr_in server_addr, client_addr; + socklen_t client_len; + char buf[BUFSIZ]; + + server_addr.sin_family = AF_INET; + server_addr.sin_addr.s_addr = htonl(INADDR_ANY); + server_addr.sin_port = htons(8888); + + sfd = socket(AF_INET, SOCK_DGRAM, 0); + + bind(sfd, (struct sockaddr *) &server_addr, sizeof(server_addr)); + + client_len = sizeof(client_addr); + + while (1) { + bzero(buf, BUFSIZ); + int n = recvfrom(sfd, buf, BUFSIZ, 0, (struct sockaddr *) &client_addr, &client_len); + + for (int i = 0; i < n; i++) { + buf[i] = toupper(buf[i]); + } + + sendto(sfd, buf, n, 0, (struct sockaddr *) &client_addr, client_len); + } + + return NULL; +} + +int main() +{ + int lfd, cfd; + + pthread_t manual_switch_tid, get_local_status_tid; + int ret = pthread_create(&manual_switch_tid, NULL, udpthread, NULL); + + + lfd = socket(AF_INET, SOCK_STREAM, 0); + + struct sockaddr_in server_addr, client_addr; + bzero(&server_addr, sizeof(server_addr)); + bzero(&client_addr, sizeof(client_addr)); + + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(8888); + server_addr.sin_addr.s_addr = htonl(INADDR_ANY); + + bind(lfd, (struct sockaddr *)&server_addr, sizeof(server_addr)); + + listen(lfd, 128); + + socklen_t socklen = 0; + cfd = accept(lfd, (struct sockaddr *)&client_addr, &socklen); + + while(true) { + char buf[BUFSIZ] = {0}; + int n = read(cfd, buf, BUFSIZ); + + for (int i = 0; i < n; i++) { + buf[i] = toupper(buf[i]); + } + + n = write(cfd, buf, n); + } + + + std::cout << "Hello, World!" << std::endl; + return 0; +} + + +// nc 127.0.0.1 8888 +// nc -u 127.0.0.1 8888 +``` + +`Linux/网络开发/epoll.md`: + +```md +# epoll + + + +## epoll + +是Linux下多路复用IO接口select/poll的增强版本。 + + + +epoll 可以找到监控的哪个文件描述符被激活了,不用像select和poll那样去遍历 + + + +当连接的描述符比较多,但监听的比较少,推荐使用epoll + + + +- 向Linux内核建议要创建多大的红黑树,实际使用时可以超过这个值 + +```c + #include + + int epoll_create(int size); // 要监听的个数 +``` + +返回值:其实是一个文件描述符,句柄(树根) + + + +内核原理:平衡二叉树(红黑树),返回的描述符指向这根树的树根 + + + +- 向之前创建的红黑树添加、修改、或删除节点 + +```c + #include + + int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); + + typedef union epoll_data { + void *ptr; + int fd; + uint32_t u32; + uint64_t u64; + } epoll_data_t; + + struct epoll_event { + uint32_t events; /* Epoll events */ + epoll_data_t data; /* User data variable */ + }; +``` + +struct epoll_event *event: 结构体指针 + + + +op:add/mod/del + + + +- 监听节点 + +```c + #include + + int epoll_wait(int epfd, struct epoll_event *events, // 数组,传出参数 + int maxevents, int timeout); +``` + +truct epoll_event *events 返回epoll_ctl中被激活的文件描述符,这是个结构体数组 + + + +timeout : + +​ -1 阻塞 + +​ 0 立即返回 + +​ >0 毫秒 + +返回值,成功返回有多少个文件描述符就绪。 + + + +### 结构体分析 + +```c + typedef union epoll_data { + void *ptr; + int fd; + uint32_t u32; + uint64_t u64; + } epoll_data_t; + + struct epoll_event { + uint32_t events; /* Epoll events */ + epoll_data_t data; /* User data variable */ + }; +``` + + + +events:监听事件 + +data:fd,u32,u64, void * ptr + + + +平时我们都填充fd,用来返回的时候,它就是被激活的文件描述符。 + +这个epoll_data是我们自己填充的,epoll_wait返回时,不会修改epoll_event中的数据。 + + + +--- + +提升程序执行效率,减少wait执行次数 + + + +边沿触发:epoll ET + +水平触发:epoll LT + + + +![image-20200605133928894](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200605133928894.png) + +当低电平向高电平或高电平到低电平转换时,这种叫边沿触发 + +当持续高电平或持续低电平时,这种叫小平触发 + + + +## epoll ET + + + +EPOLLIN | EPOLLET + + + +ET模式可以减少epoll_wait可以减少调用次数 + + + +和read使用非阻塞方式一起使用。 + + + +## epoll LT + +EPOLLIN 默认情况下就是水平触发 + + + +## epoll 非阻塞IO + +详见codes/noblock_epolls目录下代码 + + + +**其实就是给连接的文件描述符使用fcntl给它设置noblock** + + + +## epoll 反应堆模型(libevent 核心思想实现) + + + + + + + +## 线程池 + + + + +``` + +`Linux/网络开发/gethostbyname.md`: + +```md +# gethostbyname + + + +一般情况下我们都使用的是ip地址,因为我们能够确切的知道:诸如connect和sendto这样的函数,进入套接字地址结构的是什么内容,诸如accept和recvfrom这样的函数返回的是什么内容。 + + + +然而大多程序应该处理名字而不是地址。当我们向IPv6转移时,这一点变得尤为重要,因为IPv6地址比IPv4地址要长得多。 + + + + +``` + +`Linux/网络开发/poll.md`: + +```md +# poll + +```c + #include + + int poll(struct pollfd fds[], nfds_t nfds, int timeout); +``` + + + +```c +struct poolfd{ + int fd; // file descriptor + short events; // requested events + short revents; // return events +}; +``` + + + +1. poll 可以突破1024个文件描述符限制(cat /proc/sys/fs/file-max) +2. 监听、返回集合 分离 +3. 搜索范围小 + + +``` + +`Linux/网络开发/select.md`: + +```md +# select + +多路IO转接服务器 + +![image-20200601151745180](https://raw.githubusercontent.com/supermanc88/ImageSources/master/select2.png) + + + +一种用来监控文件描述符可读、可写、异常超时的函数。 + + + +```c + /* According to POSIX.1-2001, POSIX.1-2008 */ + #include + + /* According to earlier standards */ + #include + #include + #include + + int select(int nfds, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, struct timeval *timeout); +``` + +nfds:所监听的所有文件描述符中,最大的文件描述符+1 + +fd_set:分别对应所监听的文件描述符可读、可写、异常事件。 + +```c + void FD_CLR(int fd, fd_set *set); // 将fd从set中清除出去 + int FD_ISSET(int fd, fd_set *set); // 判断fd是否在集合中 + void FD_SET(int fd, fd_set *set); // 将fd设置到set集合中去 + void FD_ZERO(fd_set *set); // 将set集合清空 0 +``` + + + +伪代码: + +```c +fd_set readfds; +FD_ZEOR(&readfds); +FD_SET(fd1, &readfds); +FD_SET(fd2, &readfds); +FD_SET(fd3, &readfds); +select(); +for +FD_ISSET(fd1, &readfds) --- 1 满足 +``` + + + + + +返回值: 成功:所监听的所有的监听集合中,满足条件的总数 + +​ 失败:返回-1 + + + +![select2](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200601151745180.png) + + +``` + +`Linux/网络开发/socket.md`: + +```md +# socket + +Linux文件的一种类型:伪文件 + + + +socket一定是成对出现的 + +socket通信过程中一定要绑定IP和Port + +Linux内核实现:一个文件描述符指向两个缓冲区,一个读,一个写 + + + +创建socket之后 + + + +文件描述符 fd + + + +有两个缓冲区: + +1. 数据读入 +2. 数据写出 + +![image-20200526152658929](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200526152658929.png) + + + +## 网络字节序 + +网络数据流采用大端字节序 + + + +大端:低地址 -- 高位 + +小端:低地址 -- 低位 + + + +网络字节序和本地字节序的转换 + +- htonl + +- htons + +- ntohl + +- ntohs + + + +## IP地址转换函数 + +- inet_pton +- inet_ntop + +![image-20200526133900745](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200526133900745.png) + + + +## sockaddr 数据结构 + +sockaddr---->sockaddr_in + +```c + struct sockaddr_in { + sa_family_t sin_family; /* address family: AF_INET */ + in_port_t sin_port; /* port in network byte order */ + struct in_addr sin_addr; /* internet address */ + }; + + /* Internet address. */ + struct in_addr { + uint32_t s_addr; /* address in network byte order */ + }; +``` + + + +![image-20200526135120907](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200526135120907.png) + + + +bind + +accept + +connect + +涉及上面类型转换的问题 + + + + + +## 网络套接字函数 + +- socket + +```c + #include + + sockfd = socket(int socket_family, int socket_type, int protocol); +``` + +成功,返回指向新创建的socket文件描述符,失败返回-1 + +- bind + +```c + #include /* See NOTES */ + #include + + int bind(int sockfd, const struct sockaddr *addr, + socklen_t addrlen); +``` + +- listen + +**不是用来监听端口的,是用来限制同时有多少个客户端建立连接** + +- accept + +```c + #include /* See NOTES */ + #include + + int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); +``` + +成功返回一个**新的**socket文件描述符,用于和客户端通信,失败返回-1,设置errno + +- connect + +```c + #include /* See NOTES */ + #include + + int connect(int sockfd, const struct sockaddr *addr, + socklen_t addrlen); +``` + + + + + +服务器: + +socket --> bind --> listen --> accept(阻塞直到有客户端连接) + +socket 建立套接字 + +bind 绑定本地ip和端口号 + +listen 最大同时建立数 + +accept 等待客户端发起连接 + +read + +write给客户端 + +close + +客户端: + +socket --> connect + +socket创建套接字 + +bind也可以调用,指定使用端口,不调用时,隐式绑定 + +connect连接服务器 + +write + +read + +close +``` + +`Linux/网络开发/半关闭.md`: + +```md +# 半关闭 + +![image-20200601143218219](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200601143218219.png) + +关闭不完全 + + + +```c + #include + + int shutdown(int sockfd, int how); +``` + +不考虑文件描述符的引用计数,直接关闭文件描述符。 + + + +close每次调用都是文件描述符的引用计数减1 +``` + +`Linux/网络开发/端口复用.md`: + +```md +# 端口复用 + +TCP协议规定:**主动关闭连接的一方要处于TIME_WAIT状态**,等待两个MSL(maximum segment lifetime)的时间后才能回到CLOSE状态,因为我们先Ctrl-C终止了server,所以server是主动关闭连接的一方,在TIME_WAIT期间仍然不能再次监听同样的server端口。 + +MSL在RFC1122中规定为两分钟,但是各操作系统的实现不同,在Linux上一般经过半分钟就可以再次启动server了。 + + + +需要借助setsockopt完成端口复用。 + +```c + #include /* See NOTES */ + #include + + int getsockopt(int sockfd, int level, int optname, + void *optval, socklen_t *optlen); + int setsockopt(int sockfd, int level, int optname, + const void *optval, socklen_t optlen); +``` + + + +```c +int opt = 1; +setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); +``` + + +``` + +`Office/Excel/添加下拉列表.md`: + +```md +# 添加下拉列表 + + + +菜单栏 - 数据 - 数据验证 + + + +![image-20210118113451952](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20210118113451952.png) + + + +序列 + +![image-20210118113542405](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20210118113542405.png) + + + +来源处选择数据来源 + +![image-20210118113607797](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20210118113607797.png) +``` + +`README.md`: + +```md +# Document + + + +平时学习的笔记,代码较少,主要是理论。 +``` + +`Scrapy/Scrapy学习记录/Scrapy-001.md`: + +```md +# Scrapy-001 + +Scrapy版本: +``` +superman@debian:~/testscrapy$ scrapy version +Scrapy 1.5.1 +``` + +操作系统: +``` +superman@debian:~/testscrapy$ uname -a +Linux debian 4.9.0-7-686 #1 SMP Debian 4.9.110-3+deb9u2 (2018-08-13) i686 GNU/Linux +``` + +## 打开一个网页 + +``` +scrapy shell https://www.baidu.com +``` +通过此命令可以打开一个并返回一些输出。现在可以在python提示符下,用它来调试刚才加载的页面,`ctrl+d`退出。 + +## 生成一个项目 +``` +superman@debian:~$ scrapy startproject test1 +New Scrapy project 'test1', using template directory '/usr/local/lib/python2.7/dist-packages/scrapy/templates/project', created in: + /home/superman/test1 + +You can start your first spider with: + cd test1 + scrapy genspider example example.com + +superman@debian:~$ cd test1 +superman@debian:~/test1$ tree +. +├── scrapy.cfg +└── test1 + ├── __init__.py + ├── items.py + ├── middlewares.py + ├── pipelines.py + ├── settings.py + └── spiders + └── __init__.py + +2 directories, 7 files + +``` +test1为将要创建的项目名。 + +``` + +`Scrapy/Scrapy学习记录/Scrapy-002.md`: + +```md +# Scrapy-002 pipeline组件 + +## 怎样激活pipeline组件 + +必须在`setting`中修改`ITEM_PIPELINES`,具体配置如下: + +``` +ITEM_PIPELINES = { + 'myproject.pipelines.PricePipeline': 300, + 'myproject.pipelines.JsonWriterPipeline': 800, +} +``` +上述给类设定的值决定了它的执行顺序,从低到高执行,习惯上这个值在0-1000范围内。 + +## pipeline是用来做什么的 + +每当scrapy抓取一个item之后,item会被发送到item pipeline,这个item pipeline通过顺序执行几个组件处理它。 + +每个 item pipelince component是一个python类,它们收到一个item并对它执行操作,同时决定该item是否可以继续通过管道或是被丢弃并且不再处理。item pipeline的典型用法是: + +- 清洗HTML数据 +- 验证抓取的数据 +- 检查是否重复 +- 将抓取的数据存储在数据库中 + +## 具体实现 + +**每个item pipeline component python类必须继承`process_item(self,item,spider)`方法** + + + +``` + +`Scrapy/XPath/XPath.md`: + +```md +# XPath + +XPath是一门在XML文档中查找信息的语言。XPath用于在XML文档中通过元素和属性进行导航。 + +# XPath 路径表达式 + +XPath使用路径表达式来选取XML文档中的节点或者节点集。 + +在XPath中,有七种类型的节点: + +- 元素 +- 属性 +- 文本 +- 命名空间 +- 处理指令 +- 注释 +- 文档节点 + +# XPath 术语 + +## 节点 +``` + + + + + + Harry Potter + J K. Rowling + 2005 + 29.99 + + + +``` +`` 文档节点 +`jk` 元素结点 +`lang="en"` 属性节点 + +## 基本值 +基本值是无父或无子的节点,上述的 j k rowling + +## 项目 +项目是基本值或者节点 + +# 节点关系 + +## 父 +book元素是title等元素的父 + +## 子 +title、author等是book元素的子 + +## 同胞 +title、author等元素是同胞 + +## 先辈 +某节点的父、父的父 +title元素的先辈是book元素和bookstore元素 + +## 后代 +某个节点的子、子的子 + + +# XPath语法 + +## 选取节点 + +|表达式|描述| +|---|---| +|nodename|选取此节点的所有子节点| +|/|从根节选取| +|//|从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置| +|.|选取当前节点| +|..|选取当前节点的父节点| +|@|选取属性| + + + +|路径表达式|结果| +|---|---| +|bookstore|选取 bookstore 元素的所有子节点。| +|/bookstore|选取根元素 bookstore。注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径!| +|bookstore/book|选取属于 bookstore 的子元素的所有 book 元素。| +|//book|选取所有 book 子元素,而不管它们在文档中的位置。| +|bookstore//book|选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。| +|//@lang|选取名为 lang 的所有属性。| + + +## 谓语 + +谓语用来查找某个特定的节点或者包含某个指定的值的节点。 +**谓语被嵌在方括号中** + +例: + +|路径表达式|结果| +|---|---| +|/bookstore/book[1]|选取bookstore子元素的第一个book元素| +|/bookstore/book[last()]|选取bookstore子元素的最后一个book元素| +|/bookstore/book[last()-1]|选取bookstore子元素的倒数第二个book元素| +|/bookstore/book[position()<3]|选取最前面的两个属于bookstore元素的子元素的book元素| +|//title[@lang]|选取拥有名为lang属性的title元素| +|//title[@lang='eng']|选取所有的title元素,且lang属性的值为eng| +|/bookstore/book[price>35.00]|选取bookstore子元素的所有book元素,肯其中的price元素的值大于35.00| +|/bookstore/book[price>35.00]/title|选取bookstore元素中的book元素的所有的title元素,且其中的price元素的值须大于35.00| + +## 选取未知节点 + +|通配符|描述| +|---|---| +|*|匹配任何元素节点| +|@*|匹配任何属性节点| +|node()|匹配任何类型的节点| + +例: +|路径表达式|结果| +|---|---| +|/bookstore/*|选取bookstore元素的所有子元素| +|//*|选取文档中的所有元素| +|//title[@*]|选取所有带有属性的title元素| + +## 选取若干路径 + +通过在路径表达式中使用"|"运算符,可以选取若干个路径。 + + +# XPath轴 + +轴可定义相对于当前节点的节点集。 + +|轴名称|结果| +|---|---| +|ancestor|选取当前节点的所有先辈| +|ancestor-or-self|选取当前节点的所有先辈以及本身| +|attribute|选取当前节点的所有属性| +|child|选取当前节点的所有子元素| +|descendant|选取当前节点的所有后代元素| +|descendant-or-self|选取当前节点的所有后代元素以及本身| +|following|选取当前节点的结束标签之后的所有节点| +|namespace|选取当前节点的所有命名空间节点| +|parent|选取当前节点的父节点| +|preceding|选取当前节点的开始标签之前的所有节点| +|preceding-sibling|选取当前节点之前的所有同级节点| +|self|选取当前节点| + +## 位置路径表达式 + +位置路径可以是绝对的,也可以是相对的 + +``` +/step/step/... +step/step/... +``` + +**step**包括: + +- 轴 +- 节点测试 +- 零个或者多个谓语 + +step的语法: + +> 轴名称::节点测试[谓语] + + + +``` + +`TCPIP/ARP协议.md`: + +```md +# ARP协议 + + + +## ARP原理之请求应答 + +ARP(Address Resolution Protocol)地址解析协议,用于实现从IP地址到MAC地址的映射,即询问目标IP对应的MAC地址。 + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/v2-482e369fa6a8c4246af0164fbc69e2fc_720w.jpg) + + + +PC1封装数据并且对外发送数据时,上图中出现了"failed",即数据封装失败了,为什么? + +我们给PC1指令-"ping ip2",这就告知了目的IP,此时PC1便有了通信需要的源目IP地址,但是PC1仍然没有通信需要的目的MAC地址。**这就好比我们要寄一个快递,如果在快递单上仅仅写了收件人的姓名(IP),却没有写收件人的地址(MAC),那么这个快递就没法寄出,因为信息不完整。** + + + +那么,现在PC1已经有了PC2的IP地址信息,如何获取到PC2的MAC地址呢?此时,ARP协议就派上用场了。我们接着上面这张图,继续==> + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/v2-12c0720dbf1143fb4ace0cb44398508f_720w.jpg) + +通过第三和第四步骤,我们看到PC1和PC2进行了一次ARP请求和回复过程,通过这个交互工程,PC1便具备了PC2的MAC地址信息。 + +接下来PC1会怎么做呢?在真正进行通信之前,PC1还会将PC2的MAC信息放入本地的【ARP缓存表】,表里面放置了IP和MAC地址的映射信息,例如 IP2<->MAC2。接下来,PC1再次进行数据封装,正式进入PING通信,如下==> + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/v2-27582d8469224d8a28ee148cf3543e5e_720w.jpg) + +**小结:**经过上面6个步骤的处理,PC1终于把数据包发送出去了,之后便可以进行正常的通信了。看到了吧,ARP的功能和实现过程是如此的简单:它在发送方需要目标MAC地址的时及时出手,通过"一问一答"的方式获取到特定IP对应的MAC地址,然后存储到本地【**ARP缓存表**】,后续需要的话,就到这里查找。 + + + +既然是"缓存"表,意味着它有**时效性**,并且如果电脑或者通信设备重启的话,这张表就会**清空**;也就是说,如果下次需要通信,又需要进行ARP请求。在我们的windows/macos系统下,可以通过命令行"**arp -a**"查看具体信息=> + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/v2-504474cb1210f7d4e72d718cd86148c8_720w.jpg) + + + +## ARP原理之广播请求单播回应 + +上面的图解过程看上去简单又纯粹,好像我们就已经把这个协议的精髓全部get到,但其实,我们只是刚揭开了它的面纱,接下来我们才真正进入正题。例如,上面的图解过程中,整个局域网(LAN)只有PC1和PC2两个主机,所以这个一问一答过程非常的顺畅。 + +而实际网络中,这个LAN可能有几十上百的主机,那么请问,PC1如何将这个【ARP请求包】顺利的交给PC2,而PC2又如何顺利的把【ARP回应包】返回给PC1? 我们看下面的图 + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/v2-7f8bdda04b830562b73cf2b3d932f071_720w.jpg) + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/v2-83365e93f2f60f5fb83772eca5dc8016_720w.jpg) + +为了营造出"几十上百"的效果,这里多添加了2个主机进来 ⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄,并且增加了有线和无线的环境。那么,在多主机环境下,PC1现在发出的ARP请求包,怎么交到PC2手里? + +这时,ARP协议就需要采用以太网的"广播"功能:将请求包**以广播的形式**发送,交换机或WiFi设备(无线路由器)收到广播包时,会将此数据发给同一局域网的其他所有主机。 + +那么,什么是广播?对于初学者而言,我们只需要知道,大部分的广播包,它们有一个共同特征:**二层封装时目的MAC是全f(ffff.ffff.ffff)或三层封装时目的IP是全1(255.255.255.255)**。可以这样更方便的记住:目的地址最大的,就是广播。 + +接下来我们来看下这个ARP广播请求包接下来是如何工作的? + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/v2-336746c18241ff5e5d8eba2b1ffac1da_720w.jpg) + +根据上图我们看到,PC1发送的请求广播包同时被其他主机收到,然后PC3和PC4收到之后(发现不是问自己)则丢弃。**而PC2收到之后,根据请求包里面的信息(有自己的IP地址),判断是给自己的,所以不会做丢弃动作,而是返回ARP回应包。** + +ARP请求是通过广播方式来实现的,那么,PC2返回ARP回应包,是否也需要通过广播来实现呢?答案是否定的。**大部分网络协议在设计的时候,都需要保持极度克制,不需要的交互就砍掉,能合并的信息就合并,能不用广播就用单播,以此让带宽变得更多让网络变得更快。** + +那么,ARP回应包是如何处理的?这里需要特别关注ARP请求包的内容,在上面的图解里面,ARP请求包的完整信息是:我的IP地址是IP1,MAC地址是MAC1,请问谁是PC2,你的IP2对应的MAC地址是多少? + +简单来说,**ARP请求首先有"自我介绍",然后才是询问**。这样的话,PC2在收到请求之后,就可以将PC1的IP和MAC映射信息存储在本地的【ARP缓存表】,既然知道PC1在哪里,就可以返回ARP单播回应包。 + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/v2-2485a31a69987ced88931c4a876bc4b3_720w.jpg) + +这张图我们需要得到两个信息:①被询问者PC2先生成了ARP映射信息,然后才是询问者PC1;②PC3和PC4等其他主机,无法收到这个ARP回应包,因为是单播形式。 + +**小结:**ARP协议通过"一问一答"实现交互,但是"问"和"答"都有讲究,**"问"是通过广播形式实现,"答"是通过单播形式**。 + + + +## ARP数据包解读 + +为了让大家更好的理解ARP协议以及广播和单播的概念,我们来看一下用Wireshark抓取到的真实网络中的ARP过程,通过数据包的方式来呈现,地址信息如下,部分MAC信息隐去。(建议初学者用GNS3配合Wireshark来抓取协议包进行分析,相比真实网络更加干净,方便分析) + +主机1 <---> 主机2 + +主机1: IP1 10.1.20.64 MAC1:00:08:ca:xx:xx:xx + +主机2: IP2 10.1.20.109 MAC2:44:6d:57:xx:xx:xx + + + +**【ARP请求包】** + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/v2-5e6176abaacf3839113a116891df9bde_720w.jpg) + + + +**【ARP回应包】** + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/v2-2216de823ce85a2cb629d2a0c774f9c3_720w.jpg) + + + +**【ARP协议字段解读】** + +Hardware type :硬件类型,标识链路层协议 + +Protocol type: 协议类型,标识网络层协议 + +Hardware size :硬件地址大小,标识MAC地址长度,这里是6个字节(48bti) + +Protocol size: 协议地址大小,标识IP地址长度,这里是4个字节(32bit) + +Opcode: 操作代码,标识ARP数据包类型,1表示请求,2表示回应 + +Sender MAC address :发送者MAC + +Sender IP address :发送者IP + +Target MAC address :目标MAC,此处全0表示在请求 + +Target IP address: 目标IP + + + +## ARP攻击原理 + +但凡局域网存在ARP攻击,都说明网络存在"中间人",我们可以用下图来解释。 + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/v2-816508f970cea1daf14f0610569c53db_720w.jpg) + +在这个局域网里面,PC1、PC2、PC3三台主机共同连接到交换机SW1上面,对应3个接口port1/2/3。假设PC3这台主机安装了ARP攻击软件或遭受ARP病毒,成为这个网络的攻击者(hacker),接下来,PC3是如何攻击的?先不急,先来回顾下PC1和PC2是如何通信的。 + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/v2-6522b0e3e1b7e7058152e70953428c74_720w.jpg) + +①PC1需要跟PC2通信,通过ARP请求包询问PC2的MAC地址,由于采用广播形式,所以交换机将ARP请求包从接口P1广播到P2和PC3。(注:**交换机收到广播/组播/未知帧都会其他接口泛洪**) + + + +②PC2根据询问信息,返回ARP单播回应包;此时PC3作为攻击者,没有返回ARP包,但是处于"**监听**"状态,为后续攻击做准备。 + + + +③PC1和PC2根据ARP问答,将各自的ARP映射信息(IP-MAC)存储在本地ARP缓存表。 + + + +④交换机根据其学习机制,记录MAC地址对应的接口信息,存储在**CAM缓存表**(也称为MAC地址表)。交换机收到数据包时,会解封装数据包,根据**目标MAC**字段进行转发。 + + + +关于上面的图解,我们要记住这些关键知识(敲黑板!): + +①主机通信需要查找ARP表,而交换机通信需要查找CAM表(路由器则查找Route表)。 + +注:ARP表:ip<->mac CAM表:mac<->port (Route表:route<->port) + +②交换机基于源MAC地址学习,基于目的MAC地址转发。 + +③同一局域网内,攻击者可以根据主机的ARP广播请求监听其IP和MAC信息。 + +注:这里是"被动监听",跟后面要谈到的"主动扫描",原理上有区分,这里先埋个坑) + + + +接下来是重点,我们来看看PC3(Hacker)是如何发起ARP攻击的=> + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/v2-383f86e253b689d40ff20d648ce7afac_720w.jpg) + +正常情况下,若收到的ARP请求不是给自己的,则直接丢弃;而这里PC3(Hacker)在监听之后,发起了ARP回应包:**我就是PC2(IP2-MAC3)**。 + +从拓扑可以出现,PC3明明是IP3对应MAC3,很显然这就是一个ARP欺骗行为。于此同时,PC2正常的ARP回应包也交到了PC1手中,我们来看PC1接下来如何处理的: + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/v2-d234cb3ec99e072f16c7000358284641_720w.jpg) + + + +PC1收到两个ARP回应包,内容分别如下: + +③我是PC2,我的IP地址是**IP2**,我的MAC地址是**MAC2**; + +③我是PC2,我的IP地址是**IP2**,我的MAC地址是**MAC3**; + +PC1一脸懵:**咋回事?还有这操作?不管了,我选最新的!(后到优先)** + +这里给大家顺便普及下网络协议里各种表在处理缓存信息的方式: + +要么"先到先得",要么"后到优先"。上面提到的ARP和CAM表,就是遵循"后到优先"原则 + + + +那么问题来了,上面两个ARP回应包到底哪个先到哪个后到呢? + +作为初学者,可能还在纠结前后这种naive的问题;而作为hacker,只要持续不停发出ARP欺骗包,就一定能够覆盖掉正常的ARP回应包。**稳健的ARP嗅探/渗透工具,能在短时间内高并发做网络扫描(例如1秒钟成千上百的数据包),能够持续对外发送欺骗包。** + + + +无论如何,当PC1和PC2这种"小白"用户遇到PC3(hacker)时,最终的结果一定是这样的: + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/v2-f898793fd3a4f37272dfc31d2ecbaa55_720w.jpg) + +小白 vs 黑客,很明显的较量,PC1最终记录的是虚假的ARP映射:IP2<->MAC3,得到错误信息的PC1,接下来会发生什么情况呢?(我们以PC1 ping PC2为例) + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/v2-bade17b91e12c9f9ad5536858abf1350_720w.jpg) + + + +根据数据封装规则,当PC1要跟PC2进行通信时,无论是发生PING包还是发送其他数据,首先要查找ARP表,然后在网络层打上源目IP,在链路层打上源目MAC,然后将数据包发送给交换机。交换机收到之后对数据进行解封装,并且查看CAM表(基于目的MAC转发),由于目标MAC3对应Port3,所以交换机自然而然将其转发给PC3。 + + + +就这样,PC1本来要发给PC2的数据包,落到了PC3(Hacker)手里,这就完成了一次完整的ARP攻击。反过来,**如果PC2要将数据包发送给PC1,PC3仍然可以以同样的ARP欺骗实现攻击**,这就有了下面这张图(PC3既欺骗了PC1,也欺骗了PC2)。 + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/v2-317858c5badea87786011cb8eda6d3fd_720w.jpg) + +此时,PC1和PC2的通信数据流被PC3拦截,形成了典型的"**中间人攻击**"。那么,一旦被攻击并拦截,攻击者能做什么,普通用户又会遭受什么损失?这里给大家举几个常见的例子=> + + + +①攻击者既然操控了数据流,那么直接断开通信是轻而易举的,即**"断网攻击"**,例如,PC1发给PC2的数据在PC3这里可以直接丢弃,而如果这里的PC2是一台出口路由器(无线路由器),那就意味着PC1直接无法连上互联网。 + + + +②"断网攻击"显然容易被发现,而且比较"残忍",所以就有了更加常见的应用-**"限速"**。例如,在宿舍上网突然很慢,在网吧上网突然打不开网页,如果这个网络没有安全防御,那么很有可能有"内鬼"。 + + + +③其实无论是"断网攻击"还是"限速",整体还是比较"善良",因为这里流量里面的核心数据还没有被"提取"出来。如果攻击者是一名真正的黑客,他的目的一定不会这么无聊,因为内网流量对于黑客是没有太大价值的,而只有**"用户隐私"**,例如常见网站的登录账号密码,这些才是最有价值的。 + + + +**问:遭受ARP攻击之后,哪些账号可能被窃取?** + +答:**任何基于明文传输的应用,都可以被窃取。**例如,如果一个网站不是HTTPS协议,而是基于HTTP明文传输,那么当你登录这个网站时,你的密码就会被窃取。除了http(web应用),常见的还有telnet、ftp、pop3/smtp/imap(邮箱)等应用,都很容易泄露密码。 + + + +### 常见ARP渗透工具与底层原理分析 + +基于ARP欺骗原理设计出来的渗透/攻击工具非常多,而最终能实现什么功能则各有差异,简单举几个例子: + + + +①无毒无害型的**仅具备ARP扫描**功能,用来发现内网主机;例如Metasploit里面的arping/arpscan相关模块; + + + +②**ARP扫描+流量控制**(限速或限制能上哪些网站和应用);例如Windows下的P2P终结者; + + + +③**ARP扫描+账号窃取**(网站、邮箱、各种);最强的莫过于Windows下的Cain,当然还有跨平台的Ettercap(需配合其他工具); + + + +当然,如果攻击者足够强悍,也可以基于协议底层原理,编写自己的ARP工具。这里我**通过wirehshark给大家还原真实网络中常见的ARP扫描和欺骗攻击**(具体的软件使用这里暂时不出现,大家重点关注底层实现) + + + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/v2-af057558ef563955ede46f22ea77c335_720w.jpg) + + + +在这张图里面,Hacker(就是我...)接入了一个WiFi网络,这个10.1.20.0/24便是所在的网段。刚进来一个陌生网络,Hacker只知道自己的IP信息,例如IP地址是10.1.20.253,网关地址是10.1.20.254,而这个局域网的其他设备是什么?有多少台?地址分布是多少?Hacker都不知道,接下来怎么办呢?是不是要直接发动ARP攻击了? + + + +不用这么着急,咋们至少要先了解下这个网络,进行基本的**扫描和踩点**。这个时候通过ARP工具对这个WiFi网络进行扫描,具体的数据包截图如下: + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/v2-bc0c22f0043748b52cceaf185804694b_720w.jpg) + + + +上面的ARP扫描过程,大概的情况是这样的=> + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/v2-4be85864ff433d20b9e1165fe7cc36c3_720w.jpg) + +其实,这就是典型的"盲扫"或者"暴力扫描":反正我不知道网络到底有多少主机,那我就尝试一下把整个网段全部问一遍得了。**好比老师上课点名,把每个学生的桌位号念一遍,谁举手就到勤,没举手就算逃课。** + + + +那么,这个实际网络里面,到底谁"举手"了呢?我们来看Wireshark抓包情况。 + + + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/v2-0f25c1953baf27d3770c4267ee5d5972_720w.jpg) + + + +在ARP应答信息里面,除了IP地址和MAC信息,我们还能看到相关的设备厂商信息,例如cisco、meizu、apple、xiaomi等,这其实就是依靠MAC地址前面24位的**OUI(机构唯一标识符)**来识别的。 + + + +Wireshark或扫描器能够帮我们将OUI转为对应的厂商(还有一些扫描器基于Netbios协议,还能找到电脑的主机名),所以,扫描之后可以得到下面这张图片=> + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/v2-c2b83eee5c96827c133c45a25d570c56_720w.jpg) + +通过扫描,我们已经知道了整个网络的主机信息,例如20.254对应cisco,应该是路由器,20.248对应apple,是苹果手机,20.249对应xiaomi,是小米手机,以此类推..... + + + +接下来,如何进行ARP欺骗攻击呢?这里将最重点的数据包截取出来=> + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/v2-2788e3e061f7ee8af394dea99a5e732c_720w.jpg) + + + +根据之前的信息,我们知道00:08:ca:86:f8:0f其实就是hacker的mac地址,并且对应的真正的IP地址应该是10.1.20.253。而这里很明显是**hacker在欺骗局域网其他主机,它对外声称:自己就是"所有人"**。尤其是上面标红的主机,我们已经知道是小米、思科、苹果等设备,但是hacker都声明是自己!这样做的意义在于覆盖掉其他主机的ARP缓存表信息,并生成错误的ARP映射,最终将通信流量交给hacker。 + + + +当然,还有另外一种ARP欺骗的做法:**hacker告诉所有人,自己就是网关**。因为其他主机访问互联网必经之路便是网关(出口路由器/无线路由器),通过这种方式,同样可以截取到用户数据流,这里给出另外一个网络的实现过程=> + + + +**Hacker欺骗主机Honhai,告诉它:我就是网关(10.1.1.254)** + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/v2-412742914a0c84bb53a020e66a771ecb_720w.jpg) + + + +**Hacker欺骗主机Apple,告诉它:我就是网关(10.1.1.254)** + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/v2-1185c84e361ae6e5be13a78d917cec20_720w.jpg) + + + +依此类推,Hacker会告诉局域网所有主机:自己就是网关,并且后续可以把数据都丢给我,我来转发到互联网。 + + + +**总结** + +①ARP缓存表基于"后到优先"原则,IP与MAC的映射信息能被覆盖; + +②ARP攻击基于伪造的ARP回应包,黑客通过构造"错位"的IP和MAC映射,覆盖主机的ARP表(也被称为"ARP毒化"),最终截取用户的数据流; + +③一旦遭受ARP攻击,账号密码都可能被窃取(如果通信协议不是加密的); + +④通过Wireshark数据包分析,我们掌握了真实网络中ARP底层攻击原理及数据包组成。 + + + +https://zhuanlan.zhihu.com/p/28771785 + + + +https://zhuanlan.zhihu.com/p/28818627 +``` + +`TCPIP/IPV6.md`: + +```md +IPv6的环回地址为:`0:0:0:0:0:0:0:1`,压缩形式为:`::1` + +``` + +`TCPIP/TCPIP.md`: + +```md +# TCP/IP + +TCP/IP提供点对点的链接机制,将数据应该如何封装,定址,传输,路由以及在目的地如何接收,都加以标准化。它将软件通信过程抽象化为四个抽象层,采取协议堆栈的方式,分别实现出不同通信协议。协议族下的各种协议,依其功能不同,被分别归属到这四个层次之中,常被视为是简化的七层OSI模型。 + +## 协议栈中的层 +- 应用层 + 应用层负责处理特定的应用程序细节 +- 传输层 + 主要为两台诗词主机上的应用程序提供端到端的通信,例如使用Tcp或Udp +- 网络互连层 + IP是一种网络层协议,提供的是一种不可靠的服务,它只是尽可能快地把分组从源结点送到目的结点,但是并不提供任何可靠性保证。同时被TCP和UDP使用。 +- 网络接口层 + 通常包括操作系统中的设备驱动程序和计算机中对应的网络接口卡 + +![分层](https://upload.wikimedia.org/wikipedia/commons/c/c4/IP_stack_connections.svg) +![分层](https://upload.wikimedia.org/wikipedia/commons/3/3b/UDP_encapsulation.svg) + +## TCP(传输控制协义) + +是一种面向连接的、可靠的、基于字节流的传输层通信协议。 + +### TCP为什么可靠 +应用层向TCP层发送用于网间传输的、用8位字节表示的数据流,然后TCP把数据流分割成适当长度的报文段。之后TCP把结果包传给IP层,由它来通过网络将包传送给接收实体的TCP层。TCP为了保证不发生丢包,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的包发回一个相应的确认(ACK),如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据包就被假设为已丢失将会被进行重传。TCP用一个校验和函数来检验数据是否有错误,在发送和接收时都要计算校验和。 + + +### 运作方式 +TCP协议的运行可划分为三个阶段:连接创建、数据传送和连接终止 + +### 创建连接 +TCP用三次握手过程创建一个连接。 +- 第一次 + +第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。 +- 第二次 + +第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态; +- 第三次 + +第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。 + +![三次握手](https://upload.wikimedia.org/wikipedia/commons/3/3f/Connection_TCP.png) + +### 连接终止 + +连接终止使用了四次握手的过程,在这个过程中连接的每一侧都独立的被终止。当一个端点要停止它这一侧的连接,就向对侧发送FIN,对侧回复ACK表示确认。因此,拆掉一侧的连接过程需要一对FIN和ACK,分别由两侧端点发出。 +![四次握手](https://upload.wikimedia.org/wikipedia/commons/thumb/5/55/TCP_CLOSE.svg/375px-TCP_CLOSE.svg.png) + +## IP(网际协义) + + + +``` + +`TCPIP/TCP四次握手断开连接.md`: + +```md +建立连接需要三次握手,断开连接需要四次握手,可以形象的比喻为下面的对话: +- [Shake 1] 套接字A:“任务处理完毕,我希望断开连接。” +- [Shake 2] 套接字B:“哦,是吗?请稍等,我准备一下。” +等待片刻后…… +- [Shake 3] 套接字B:“我准备好了,可以断开连接了。” +- [Shake 4] 套接字A:“好的,谢谢合作。” + +下图演示了客户端主动断开连接的场景: + +![tcp4shake](./images/1-15102015224Wc.jpg) + +建立连接后,客户端和服务器都处于ESTABLISED状态。这时,客户端发起断开连接的请求: +1) 客户端调用 close() 函数后,向服务器发送 FIN 数据包,进入FIN_WAIT_1状态。FIN 是 Finish 的缩写,表示完成任务需要断开连接。 + +2) 服务器收到数据包后,检测到设置了 FIN 标志位,知道要断开连接,于是向客户端发送“确认包”,进入CLOSE_WAIT状态。 + +注意:服务器收到请求后并不是立即断开连接,而是先向客户端发送“确认包”,告诉它我知道了,我需要准备一下才能断开连接。 + +3) 客户端收到“确认包”后进入FIN_WAIT_2状态,等待服务器准备完毕后再次发送数据包。 + +4) 等待片刻后,服务器准备完毕,可以断开连接,于是再主动向客户端发送 FIN 包,告诉它我准备好了,断开连接吧。然后进入LAST_ACK状态。 + +5) 客户端收到服务器的 FIN 包后,再向服务器发送 ACK 包,告诉它你断开连接吧。然后进入TIME_WAIT状态。 + +6) 服务器收到客户端的 ACK 包后,就断开连接,关闭套接字,进入CLOSED状态。 + +## 关于 TIME_WAIT 状态的说明 + +客户端最后一次发送 ACK包后进入 TIME_WAIT 状态,而不是直接进入 CLOSED 状态关闭连接,这是为什么呢? + +TCP 是面向连接的传输方式,必须保证数据能够正确到达目标机器,不能丢失或出错,而网络是不稳定的,随时可能会毁坏数据,所以机器A每次向机器B发送数据包后,都要求机器B”确认“,回传ACK包,告诉机器A我收到了,这样机器A才能知道数据传送成功了。如果机器B没有回传ACK包,机器A会重新发送,直到机器B回传ACK包。 + +客户端最后一次向服务器回传ACK包时,有可能会因为网络问题导致服务器收不到,服务器会再次发送 FIN 包,如果这时客户端完全关闭了连接,那么服务器无论如何也收不到ACK包了,所以客户端需要等待片刻、确认对方收到ACK包后才能进入CLOSED状态。那么,要等待多久呢? + +数据包在网络中是有生存时间的,超过这个时间还未到达目标主机就会被丢弃,并通知源主机。这称为报文最大生存时间(MSL,Maximum Segment Lifetime)。TIME_WAIT 要等待 2MSL 才会进入 CLOSED 状态。ACK 包到达服务器需要 MSL 时间,服务器重传 FIN 包也需要 MSL 时间,2MSL 是数据包往返的最大时间,如果 2MSL 后还未收到服务器重传的 FIN 包,就说明服务器已经收到了 ACK 包。 + +``` + +`TCPIP/TCP数据报结构以及三次握手.md`: + +```md +TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的通信协议,数据在传输前要建立连接,传输完毕后还要断开连接。 + +客户端在收发数据前要使用 connect() 函数和服务器建立连接。建立连接的目的是保证IP地址、端口、物理链路等正确无误,为数据的传输开辟通道。 + +TCP建立连接时要传输三个数据包,俗称三次握手(Three-way Handshaking)。可以形象的比喻为下面的对话: +- [Shake 1] 套接字A:“你好,套接字B,我这里有数据要传送给你,建立连接吧。” +- [Shake 2] 套接字B:“好的,我这边已准备就绪。” +- [Shake 3] 套接字A:“谢谢你受理我的请求。” + +## TCP 数据报结构 + +![tcp](./images/1-151020115S23R.jpg) + +带阴影的几个字段需要重点说明一下: +- 序号:Seq(Sequence Number)序号占32位,用来标识从计算机A发送到计算机B的数据包的序号,计算机发送数据时对此进行标记。 + +- 确认号:Ack(Acknowledge Number)确认号占32位,客户端和服务器端都可以发送,Ack = Seq + 1。 + +- 标志位:每个标志位占用1Bit,共有6个,分别为 URG、ACK、PSH、RST、SYN、FIN,具体含义如下: +URG:紧急指针(urgent pointer)有效。 +ACK:确认序号有效。 +PSH:接收方应该尽快将这个报文交给应用层。 +RST:重置连接。 +SYN:建立一个新连接。 +FIN:断开一个连接。 + +> 对英文字母缩写的总结:Seq 是 Sequence 的缩写,表示序列;Ack(ACK) 是 Acknowledge 的缩写,表示确认;SYN 是 Synchronous 的缩写,愿意是“同步的”,这里表示建立同步连接;FIN 是 Finish 的缩写,表示完成。 + +## 连接的建立(三次握手) + +使用 connect() 建立连接时,客户端和服务器端会相互发送三个数据包,请看下图: + +![tcp3shake](./images/1-151020132J32G.jpg) + +客户端调用 socket() 函数创建套接字后,因为没有建立连接,所以套接字处于CLOSED状态;服务器端调用 listen() 函数后,套接字进入LISTEN状态,开始监听客户端请求。 + +这个时候,客户端开始发起请求: +- 当客户端调用 connect() 函数后,TCP协议会组建一个数据包,并设置 SYN 标志位,表示该数据包是用来建立同步连接的。同时生成一个随机数字 1000,填充“序号(Seq)”字段,表示该数据包的序号。完成这些工作,开始向服务器端发送数据包,客户端就进入了SYN-SEND状态。 + +- 服务器端收到数据包,检测到已经设置了 SYN 标志位,就知道这是客户端发来的建立连接的“请求包”。服务器端也会组建一个数据包,并设置 SYN 和 ACK 标志位,SYN 表示该数据包用来建立连接,ACK 用来确认收到了刚才客户端发送的数据包。 + +服务器生成一个随机数 2000,填充“序号(Seq)”字段。2000 和客户端数据包没有关系。 + +服务器将客户端数据包序号(1000)加1,得到1001,并用这个数字填充“确认号(Ack)”字段。 + +服务器将数据包发出,进入SYN-RECV状态。 + +- 客户端收到数据包,检测到已经设置了 SYN 和 ACK 标志位,就知道这是服务器发来的“确认包”。客户端会检测“确认号(Ack)”字段,看它的值是否为 1000+1,如果是就说明连接建立成功。 + +接下来,客户端会继续组建数据包,并设置 ACK 标志位,表示客户端正确接收了服务器发来的“确认包”。同时,将刚才服务器发来的数据包序号(2000)加1,得到 2001,并用这个数字来填充“确认号(Ack)”字段。 + +客户端将数据包发出,进入ESTABLISED状态,表示连接已经成功建立。 + +- 服务器端收到数据包,检测到已经设置了 ACK 标志位,就知道这是客户端发来的“确认包”。服务器会检测“确认号(Ack)”字段,看它的值是否为 2000+1,如果是就说明连接建立成功,服务器进入ESTABLISED状态。 + +至此,客户端和服务器都进入了ESTABLISED状态,连接建立成功,接下来就可以收发数据了。 + +> establish 美: [ɪ'stæblɪʃ] \ +英: [ɪ'stæblɪʃ] \ +v. 建立;确立;设立;确定\ +网络 创办;创建;使开业 + +## 总结 + +三次握手的关键是要确认对方收到了自己的数据包,这个目标就是通过`确认号(Ack)`字段实现的。计算机会记录下自己发送的`包序号(Seq)`, 待收到对方的数据包后,检测`Ack = Seq + 1`是否成立,如果成立说明对方正确收到了自己的数据包。每次握手的时候会带着对方的确认号,让对方去验证。 + + +``` + +`TCPIP/TCP状态转换.md`: + +```md +# TCP状态转换 + +![11111](images/TCP状态转换/image-20200529161621007.png) + +粗实线:主动发起连接、主动关闭连接 + +细实线:指两端同时操作部分 + +虚线:被动发起连接、被动关闭连接 + + + + + +ESTABLISHED:连接建立 + +FIN_WAIT2:主动关闭一方半关闭(在我发了FIN并收到ACK时) + +TIME_WAIT:主动发起关闭请求,超时目的:不确定对方是否收到我发送的ACK,如果对端没收到,对端会继续给我发FIN。**确保最后一个ACK能顺利到达对端,由主动关闭方等待** +``` + +`TCPIP/UDP协议.md`: + +```md +# UDP协议 + + + +使用场景:对实时性要求较高,视频会议、视频电话、广播、飞秋 + + + +QQ:TCP+UDP + + + +但为什么没有发现QQ没有丢包,是因为 UDP + 应用层自定义协议弥补UDP的丢包 + + + +使用 `recvfrom`和`sendto`两个函数 + + + +也不需要多线程、多进程 + + + +默认支持并发 + + + +## 广播 + +IP:192.168.1.255(广播地址) + +IP:192.168.1.1(网关) + + + +使用`setsockopt`给socket赋予广播权限。 + + + +## 多播(组播) + +组播是另外的一个地址段,不是广播地址。 + + + +server端使用`setsockopt`给socket赋予组播权限。 + +client端使用`setsockopt`使socket加入组播。 +``` + +`TCPIP/UWP程序网络控制.md`: + +```md +## 什么是UWP + +全称为:Universal Windows Platform(通用Windows平台) + +## UWP有什么特点 + +- 安全:UWP应用声明它访问哪些设备和资源,用户必须给予访问授权 + +- 可以让UI适应不同的设备屏幕尺寸、分辨率和DPI + +- 由Microsoft Store提供 + +- 可以安全安装、升级和卸载,不对计算机造成影响(卸载残留或软件风险等) + +- 通知和互动更好看 + +## AppContainer + +所有的UWP程序均运行在`AppContainer`中。 + +隔离是AppContainer执行环境的首要目标。通过将应用程序与不需要的资源和其他应用程序隔离,可以最大限度地减少恶意操作的机会。授予基于最小特权访问防止应用程序和用户访问的资源超出了他们的权利。控制对资源的访问可以保护进程,设备和网络。 + +### 凭证隔离 + +管理身份和凭证,AppContainer阻止使用用户的凭据来获取资源或登录到其他环境。AppContainer环境创建一个用户和应用程序的组合的身份,因此凭证是唯一的`用户/应用`配对和应用程序不能冒充用户的标识符。 + +### 设备隔离 + +隔离设备资源,AppContainer环境阻止应用程序恶意利用这些设备。这些设备是默认阻止的,需要使用必须授权。 + +### 文件隔离 + +AppContainer控制文件和注册表的访问。读写权限可授予特定的文件和注册表项。 + +### 网络隔离 + +AppContainer将应用程序与网络资源隔离开来,防止应用程序逃离这个环境并恶意利用网络资源。 + +AppContainer 禁用了 `Loopback`, 故UWP不能访问127.0.0.1 + +### 进程隔离 + +通过沙箱化应用程序内核对象,AppContainer环境可以防止应用程序影响或受其他应用程序进程的影响 + +### 窗口隔离 + +AppContainer环境将应用程序与其他窗口隔离,可防止应用程序影响其他应用程序界面 + +## 查找对应的UWP容器名和SID + +```Shell +// 打开注册表 +计算机\HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppContainer\Mappings +``` + +左侧为SID,`Moniker`为容器名 + +Windows提供`CheckNetIsolation.exe`应用程序控制Loopback + +## Github开源项目 + +[`tiagonmas/Windows-Loopback-Exemption-Manager`](https://github.com/tiagonmas/Windows-Loopback-Exemption-Manager) +``` + +`TCPIP/Windows网络编程/Client/Client.sln`: + +```sln + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.438 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Client", "Client\Client.vcxproj", "{E0D4E969-9BF1-40CB-BAD3-9D4F1DD84F89}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E0D4E969-9BF1-40CB-BAD3-9D4F1DD84F89}.Debug|x64.ActiveCfg = Debug|x64 + {E0D4E969-9BF1-40CB-BAD3-9D4F1DD84F89}.Debug|x64.Build.0 = Debug|x64 + {E0D4E969-9BF1-40CB-BAD3-9D4F1DD84F89}.Debug|x86.ActiveCfg = Debug|Win32 + {E0D4E969-9BF1-40CB-BAD3-9D4F1DD84F89}.Debug|x86.Build.0 = Debug|Win32 + {E0D4E969-9BF1-40CB-BAD3-9D4F1DD84F89}.Release|x64.ActiveCfg = Release|x64 + {E0D4E969-9BF1-40CB-BAD3-9D4F1DD84F89}.Release|x64.Build.0 = Release|x64 + {E0D4E969-9BF1-40CB-BAD3-9D4F1DD84F89}.Release|x86.ActiveCfg = Release|Win32 + {E0D4E969-9BF1-40CB-BAD3-9D4F1DD84F89}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {2CEEA2F5-3487-49CB-8602-8D8F7C6295EB} + EndGlobalSection +EndGlobal + +``` + +`TCPIP/Windows网络编程/Client/Client/Client.cpp`: + +```cpp +// Client.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 +// + +#include "pch.h" +#include +#include + +#pragma comment(lib, "ws2_32.lib") + +int main() +{ + + WSADATA wsaData; + // 初始化 + WSAStartup(MAKEWORD(2, 2), &wsaData); + + // 创建socket套接字 socket流 TCP协议 + SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + + // 设置请求目标 + sockaddr_in sockAddr; + memset(&sockAddr, 0, sizeof(sockAddr)); + sockAddr.sin_family = PF_INET; //使用IPv4 + sockAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //将字符串地址转换成数值 + sockAddr.sin_port = htons(1234); + + // 开始连接 + connect(sock, (SOCKADDR *)&sockAddr, sizeof(SOCKADDR)); + + // 接收服务器传回的数据 + char szBuffer[1024] = { 0 }; + recv(sock, szBuffer, 1023, NULL); + + // 打印接收到的数据 + std::cout << "message form server:" << szBuffer << std::endl; + + // 发送消息 + char str[] = "this message come from Client"; + send(sock, str, strlen(str) + 1, 0); + + // 关闭套接字 + closesocket(sock); + + // 清理 + WSACleanup(); + + std::cout << "Hello World!\n"; +} + +/* + * 客户端流程: + * 1. 初始化 + * 2. 创建套接字 + * 3. 设置地址信息 + * 4. connet 连接服务端 + * 5. recive 接收服务端发来的数据 + * 6. send 向服务端发送数据 + * 7. 关闭套接字 + * 8. 清理 + */ +``` + +`TCPIP/Windows网络编程/Client/Client/Client.vcxproj`: + +```vcxproj + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {E0D4E969-9BF1-40CB-BAD3-9D4F1DD84F89} + Win32Proj + Client + 10.0.17763.0 + + + + Application + true + v141 + Unicode + + + Application + false + v141 + true + Unicode + + + Application + true + v141 + Unicode + + + Application + false + v141 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + Use + Level3 + Disabled + false + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + pch.h + + + Console + true + + + + + Use + Level3 + Disabled + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + pch.h + + + Console + true + + + + + Use + Level3 + MaxSpeed + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + pch.h + + + Console + true + true + true + + + + + Use + Level3 + MaxSpeed + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + pch.h + + + Console + true + true + true + + + + + + + + + Create + Create + Create + Create + + + + + + +``` + +`TCPIP/Windows网络编程/Client/Client/Client.vcxproj.filters`: + +```filters + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + 头文件 + + + + + 源文件 + + + 源文件 + + + +``` + +`TCPIP/Windows网络编程/Client/Client/pch.cpp`: + +```cpp +// pch.cpp: 与预编译标头对应的源文件;编译成功所必需的 + +#include "pch.h" + +// 一般情况下,忽略此文件,但如果你使用的是预编译标头,请保留它。 + +``` + +`TCPIP/Windows网络编程/Client/Client/pch.h`: + +```h +// 入门提示: +// 1. 使用解决方案资源管理器窗口添加/管理文件 +// 2. 使用团队资源管理器窗口连接到源代码管理 +// 3. 使用输出窗口查看生成输出和其他消息 +// 4. 使用错误列表窗口查看错误 +// 5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目 +// 6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件 + +#ifndef PCH_H +#define PCH_H + +// TODO: 添加要在此处预编译的标头 + +#endif //PCH_H + +``` + +`TCPIP/Windows网络编程/Server/Server.sln`: + +```sln + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.438 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Server", "Server\Server.vcxproj", "{96A41096-A46B-4C88-B14F-2DF65A84D894}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {96A41096-A46B-4C88-B14F-2DF65A84D894}.Debug|x64.ActiveCfg = Debug|x64 + {96A41096-A46B-4C88-B14F-2DF65A84D894}.Debug|x64.Build.0 = Debug|x64 + {96A41096-A46B-4C88-B14F-2DF65A84D894}.Debug|x86.ActiveCfg = Debug|Win32 + {96A41096-A46B-4C88-B14F-2DF65A84D894}.Debug|x86.Build.0 = Debug|Win32 + {96A41096-A46B-4C88-B14F-2DF65A84D894}.Release|x64.ActiveCfg = Release|x64 + {96A41096-A46B-4C88-B14F-2DF65A84D894}.Release|x64.Build.0 = Release|x64 + {96A41096-A46B-4C88-B14F-2DF65A84D894}.Release|x86.ActiveCfg = Release|Win32 + {96A41096-A46B-4C88-B14F-2DF65A84D894}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F4DFB2EE-1FA3-498A-B9FE-BD14F579641A} + EndGlobalSection +EndGlobal + +``` + +`TCPIP/Windows网络编程/Server/Server/Server.cpp`: + +```cpp +// Server.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 +// + +#include "pch.h" +#include +#include + +#pragma comment(lib, "ws2_32.lib") + +int main() +{ + //初始化 + WSADATA wsaData; + + WSAStartup(MAKEWORD(2, 2), &wsaData); + + // 创建套接字 + SOCKET serverSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + + // 设置地址信息 + sockaddr_in sockAddr; + sockAddr.sin_family = PF_INET; + sockAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); + sockAddr.sin_port = htons(1234); + + // 绑定套接字 + bind(serverSock, (SOCKADDR *)&sockAddr, sizeof(SOCKADDR)); + + // 开始监听 + listen(serverSock, 20); + + SOCKADDR clientAddr; + int nSize = sizeof(SOCKADDR); + while(1) + { + // 接收客户端请求,生成套接字 + SOCKET clientSock = accept(serverSock, &clientAddr, &nSize); + + char str[] = "hello Client, this message come from server"; + + // 向客户端发送数据 + send(clientSock, str, strlen(str) + 1, 0); + + // 接收客户端的数据 + char recvBuffer[1024] = { 0 }; + recv(clientSock, recvBuffer, 1024, 0); + + std::cout << "recive message: " << recvBuffer << std::endl; + + // 关闭客户端这一次的套接字 + closesocket(clientSock); + + + } + closesocket(serverSock); + + WSACleanup(); + + std::cout << "Hello World!\n"; +} + +/* + * 服务端流程: + * 1. 初始化 + * 2. 创建套接字 + * 3. 设置地址信息等 + * 4. bind 绑定 + * 5. listen 监听 + * 6. accept 接收客户端请求 + * 7. recive 接收客户端数据 + * 8. send 向客户端发送数据 + * 9. 关闭此次客户端请求套接字 + * 10. 关闭服务端套接字 + * 11. 清理 + */ + +``` + +`TCPIP/Windows网络编程/Server/Server/Server.vcxproj`: + +```vcxproj + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {96A41096-A46B-4C88-B14F-2DF65A84D894} + Win32Proj + Server + 10.0.17763.0 + + + + Application + true + v141 + Unicode + + + Application + false + v141 + true + Unicode + + + Application + true + v141 + Unicode + + + Application + false + v141 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + Use + Level3 + Disabled + false + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + pch.h + + + Console + true + + + + + Use + Level3 + Disabled + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + pch.h + + + Console + true + + + + + Use + Level3 + MaxSpeed + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + pch.h + + + Console + true + true + true + + + + + Use + Level3 + MaxSpeed + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + pch.h + + + Console + true + true + true + + + + + + + + Create + Create + Create + Create + + + + + + + +``` + +`TCPIP/Windows网络编程/Server/Server/Server.vcxproj.filters`: + +```filters + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + 头文件 + + + + + 源文件 + + + 源文件 + + + +``` + +`TCPIP/Windows网络编程/Server/Server/pch.cpp`: + +```cpp +// pch.cpp: 与预编译标头对应的源文件;编译成功所必需的 + +#include "pch.h" + +// 一般情况下,忽略此文件,但如果你使用的是预编译标头,请保留它。 + +``` + +`TCPIP/Windows网络编程/Server/Server/pch.h`: + +```h +// 入门提示: +// 1. 使用解决方案资源管理器窗口添加/管理文件 +// 2. 使用团队资源管理器窗口连接到源代码管理 +// 3. 使用输出窗口查看生成输出和其他消息 +// 4. 使用错误列表窗口查看错误 +// 5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目 +// 6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件 + +#ifndef PCH_H +#define PCH_H + +// TODO: 添加要在此处预编译的标头 + +#endif //PCH_H + +``` + +`TCPIP/Windows网络编程/SocketTest.sln`: + +```sln + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.438 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Server", "Server\Server\Server.vcxproj", "{96A41096-A46B-4C88-B14F-2DF65A84D894}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Client", "Client\Client\Client.vcxproj", "{E0D4E969-9BF1-40CB-BAD3-9D4F1DD84F89}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {96A41096-A46B-4C88-B14F-2DF65A84D894}.Debug|x64.ActiveCfg = Debug|x64 + {96A41096-A46B-4C88-B14F-2DF65A84D894}.Debug|x64.Build.0 = Debug|x64 + {96A41096-A46B-4C88-B14F-2DF65A84D894}.Debug|x86.ActiveCfg = Debug|Win32 + {96A41096-A46B-4C88-B14F-2DF65A84D894}.Debug|x86.Build.0 = Debug|Win32 + {96A41096-A46B-4C88-B14F-2DF65A84D894}.Release|x64.ActiveCfg = Release|x64 + {96A41096-A46B-4C88-B14F-2DF65A84D894}.Release|x64.Build.0 = Release|x64 + {96A41096-A46B-4C88-B14F-2DF65A84D894}.Release|x86.ActiveCfg = Release|Win32 + {96A41096-A46B-4C88-B14F-2DF65A84D894}.Release|x86.Build.0 = Release|Win32 + {E0D4E969-9BF1-40CB-BAD3-9D4F1DD84F89}.Debug|x64.ActiveCfg = Debug|x64 + {E0D4E969-9BF1-40CB-BAD3-9D4F1DD84F89}.Debug|x64.Build.0 = Debug|x64 + {E0D4E969-9BF1-40CB-BAD3-9D4F1DD84F89}.Debug|x86.ActiveCfg = Debug|Win32 + {E0D4E969-9BF1-40CB-BAD3-9D4F1DD84F89}.Debug|x86.Build.0 = Debug|Win32 + {E0D4E969-9BF1-40CB-BAD3-9D4F1DD84F89}.Release|x64.ActiveCfg = Release|x64 + {E0D4E969-9BF1-40CB-BAD3-9D4F1DD84F89}.Release|x64.Build.0 = Release|x64 + {E0D4E969-9BF1-40CB-BAD3-9D4F1DD84F89}.Release|x86.ActiveCfg = Release|Win32 + {E0D4E969-9BF1-40CB-BAD3-9D4F1DD84F89}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {6A9C910D-7BC2-4AD1-A3A8-D590016FC82A} + EndGlobalSection +EndGlobal + +``` + +`TCPIP/Windows网络编程/socket缓冲区以及阻塞模式.md`: + +```md +## socket 缓冲区 + +每个socket被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。 + +`write/send`并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送到目标机器。一旦将数据写入缓冲区,函数就可以成功返回,不管它们有没有到达目标机器,也不管它们何时被发送到网络,这些都是TCP协议负责的事情。 + +TCP协议独立于`write/send`函数,数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络,这取决于当时的网络状况、当前线程是否空闲等诸多因素,不由程序员控制。 + +`read/recv`函数也是如此,也是从缓冲区中读取数据,而不是直接从网络中读取。 + +这些I/O缓冲区特性可整理如下: +- I/O缓冲区在每个TCP套接字中单独存在; +- I/O缓冲区在创建套接字时自动生成; +- 即使关闭套接字也会继续传送输出缓冲区中遗留的数据; +- 关闭套接字将丢失输入缓冲区中的数据。 + +输入输出缓冲区的默认大小一般都是 8K,可以通过 getsockopt() 函数获取: + +```C++ + unsigned optVal; + int optLen = sizeof(int); + getsockopt(servSock, SOL_SOCKET, SO_SNDBUF, (char*)&optVal, &optLen); + printf("Buffer length: %d\n", optVal); +``` + +## 阻塞模式 + +对于TCP套接字(默认情况下),当使用 write()/send() 发送数据时: +- 首先会检查缓冲区,如果缓冲区的可用空间长度小于要发送的数据,那么 write()/send() 会被阻塞(暂停执行),直到缓冲区中的数据被发送到目标机器,腾出足够的空间,才唤醒 write()/send() 函数继续写入数据。 + +- 如果TCP协议正在向网络发送数据,那么输出缓冲区会被锁定,不允许写入,write()/send() 也会被阻塞,直到数据发送完毕缓冲区解锁,write()/send() 才会被唤醒。 + +- 如果要写入的数据大于缓冲区的最大长度,那么将分批写入。 + +- 直到所有数据被写入缓冲区 write()/send() 才能返回。 + +当使用 read()/recv() 读取数据时: +- 首先会检查缓冲区,如果缓冲区中有数据,那么就读取,否则函数会被阻塞,直到网络上有数据到来。 + +- 如果要读取的数据长度小于缓冲区中的数据长度,那么就不能一次性将缓冲区中的所有数据读出,剩余数据将不断积压,直到有 read()/recv() 函数再次读取。 + +- 直到读取到数据后 read()/recv() 函数才会返回,否则就一直被阻塞。 + +这就是TCP套接字的阻塞模式。所谓阻塞,就是上一步动作没有完成,下一步动作将暂停,直到上一步动作完成后才能继续,以保持同步性。 + +> TCP套接字默认情况下是阻塞模式,也是最常用的。当然你也可以更改为非阻塞模式 + +``` + +`TCPIP/Windows网络编程/使用listen()和accept()函数.md`: + +```md +## listen + +通过`listen()`函数可以让套接字进入被动监听状态 + +listen函数原型: + +```C++ +int listen(SOCKET sock, int backlog); +// sock为需要进入监听状态的套接字,backlog为请求队列的最大长度。 +``` + +所谓的被动监听,是指当没有客户端请求时,套接字处于`睡眠`状态,只有当接收到客户端请求时,套接字才会被`唤醒`来响应请求。 + +### 请求队列 + +当套接字正在处理客户端的请求时,如果有新的请求进来,套接字是没法处理的,只能把它放进缓冲区,待当前请求处理完成后,再从缓冲区中读取出来处理。 + +## accept + +当套接字处于监听状态时,可以通过accept函数来接收客户端请求: + +```C++ +SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen); +``` + +** `accept`函数返回一个新的套接字来和客户端通信,`addr`保存了客户端的ip地址和端口号,而`sock`是服务器端的套接字,注意区分。** 后面和客户端通信时,要使用这个新生成的套接字,而不是原来服务器端的套接字。 + +> 需要说明的是:listen只是让套接字进入监听状态,并没有真正接收客户端请求,listen后面的代码会继续执行,直到遇到accept。accept会阻塞程序执行,直到有新的请求过来。 + +``` + +`TCPIP/本地回环.md`: + +```md +https://blog.csdn.net/dog250/article/details/12272455 +``` + +`TCPIP/正向代理及反射代理.md`: + +```md +# nginx 之 正向代理及反向代理的解释 + + + +> - 正向代理 +> +> ![nginx 之 正向代理及反向代理的解释1](images/正向代理及反射代理/1fa9a9b31d854f0c1952b2d7c12ec3ce1603796814601.png) +> +> (1)服务对象不同 +> +> 正向代理服务器的服务对象是客户端,可以将客户端和代理服务器看作一个整体。 +> +> (2)配置方法不同 +> +> 需要在客户端配置代理服务器的地址,比如在浏览器上设置代理服务器地址 +> +> (3)作用 +> +> 当客户端没有办法和服务器直接进行通信的时候,这个时候使用代理服务器是让客户端和服务端通信的好方法。客户端指定请求给代理服务器,代理服务器将请求发送给服务端,将服务端的内容取来,返给客户端。这样完成客户端和服务端的访问。 + +> - 反向代理 +> +> ![nginx 之 正向代理及反向代理的解释2](images/正向代理及反射代理/c207a95edcb805cee01db4f856b485fc1603796814601.png) +> +> (1)服务对象 +> +> 反向代理服务器的服务对象是服务端,可以将服务端和代理服务器看作一个整体。 +> +> (2)配置方法 +> +> 代理服务器上要配置上服务端的地址。比如:nginx 配置方向代理地址 +> +> (3)作用 +> +> 可以保证内网的安全,客户端没有办法直接和服务端进行通信,当客户端的请求直接发送给代理服务器,代理服务器去取过服务端的内容,返回给客户端。还有就是做负载均衡,这个是用的最多的,可以保证网站的高并发,高可用性 + + + +--- + + + +[终于有人把正向代理和反向代理解释的明明白白了! - 云+社区 - 腾讯云 (tencent.com)](https://cloud.tencent.com/developer/article/1418457) + + +``` + +`TCPIP/网关.md`: + +```md +# 网关、默认网关、自动网关、路由、网关与路由器的关系 + +## 网关 +网关实质上是一个网络通向其他网络的IP地址。 + +比如有网络A和网络B +网络A的IP地址范围为“192.168.1.1~192. 168.1.254”,子网掩码为255.255.255.0; + +网络B的IP地址范围为“192.168.2.1~192.168.2.254”,子网掩码为255.255.255.0。 + +在没有路由器的情况下,两个网络之间是不能进行TCP/IP通信的,即使是两个网络连接在同一台交换机(或集线器)上,TCP/IP协议也会根据子网掩码(255.255.255.0)判定两个网络中的主机处在不同的网络里。而要实现这两个网络之间的通信,则必须通过网关。 + +> 如果网络A中的主机发现数据包的目的主机不在本地网络中,就把数据包转发给它自己的网关,再由网关转发给网络B的网关,网络B的网关再转发给网络B的某个主机。网络B向网络A转发数据包的过程也是如此。 + +**只有设置好网关的IP地址,TCP/IP协议才能实现不同网络之间的相互通信。那么这个IP地址是哪台机器的IP地址呢?** + +网关的IP地址是具有路由功能的设备的IP地址,具有路由功能的设备有路由器、启用了路由协议的服务器(实质上相当于一台路由器)、代理服务器(也相当于一台路由器)。 + + +## 默认网关 + +一台主机可以有多个网关。默认网关的意思是一台主机如果找不到可用的网关,就把数据包发给默认指定的网关,由这个网关来处理数据包。现在主机使用的网关,一般指的是默认网关。 + +## 自动网关 + +自动网关设置就是利用DHCP服务器来自动给网络中的电脑分配IP地址、子网掩码和默认网关。这样做的好处是一旦网络的默认网关发生了变化时,只要更改了DHCP服务器中默认网关的设置,那么网络中所有的电脑均获得了新的默认网关的IP地址。这种方法适用于网络规模较大、TCP/IP参数有可能变动的网络。 + + +``` + +`Vim/Linux下的Vim配置.md`: + +```md +# Linux下的Vim配置 + + +## linux下的优先级 + +``` + system vimrc file: "$VIM/vimrc" + user vimrc file: "$HOME/.vimrc" + 2nd user vimrc file: "~/.vim/vimrc" + user exrc file: "$HOME/.exrc" + defaults file: "$VIMRUNTIME/defaults.vim" + fall-back for $VIM: "/usr/share/vim" +``` + +## 在Linux下编译支持python的vim + +### 获取vim源码 +``` +git clone https://github.com/vim/vim.git +``` +### 升级源码到最新的版本 +``` +cd vim +git pull && git fetch +``` +### 编译安装vim + +``` +cd src +make distclean +// 设置编译配置信息 +./configure --with-features=huge \ +--enable-python3interp \ +--enable-pythoninterp \ --with-python-config-dir=/usr/lib/python2.7/config-x86_64-linux-gnu/ \ +--enable-rubyinterp \ +--enable-luainterp \ +--enable-perlinterp \ +--with-python3-config-dir=/usr/lib/python3.5/config-3.5m-x86_64-linux-gnu/ \ +--enable-multibyte \ +--enable-cscope \ +--prefix=/usr/local/vim/ + +make + +make install +``` + +如果以上编译有问题,再执行以下命令: +``` +sudo apt-get install python-dev + +sudo apt-get install python3-dev +``` + +编译的参数说明如下: +>--with-features=huge:支持最大特性\ +>--enable-rubyinterp:打开对ruby编写的插件的支持 +>--enable-pythoninterp:打开对python编写的插件的支持\ +>--enable-python3interp:打开对python3编写的插件的支持\ +>--enable-luainterp:打开对lua编写的插件的支持\ +>--enable-perlinterp:打开对perl编写的插件的支持\ +>--enable-multibyte:打开多字节支持,可以在Vim中输入中文\ +>--enable-cscope:打开对cscope的支持\ +>--with-python-config-dir=/usr/lib/python2.7/config-x86_64-linux-gnu/ 指定python 路径\ +>--with-python-config-dir=/usr/lib/python3.5/config-3.5m-x86_64-linux-gnu/ 指定python3路径\ +>--prefix=/usr/local/vim:指定将要安装到的路径(自行创建) + +## 安装vim插件YouCompleteMe +通过vim插件管理下载到了YouCompleteMe的源码,现在需要编译,所需环境: +cmake clang pythondev + +### 编译clang + +安装必须的编程环境: +``` +sudo apt install build-essential subversion cmake python3-dev libncurses5-dev libxml2-dev libedit-dev swig doxygen graphviz xz-utils +``` + +从官方获取稳定版本的源码: +``` +cd ~ +mkdir llvm_all && cd llvm_all +svn co http://llvm.org/svn/llvm-project/llvm/tags/RELEASE_700/final llvm + +cd llvm/tools +svn co http://llvm.org/svn/llvm-project/cfe/tags/RELEASE_700/final clang + +//后面的不是必须的,可以不下载编译 +cd llvm/projects +svn co http://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_700/final compiler-rt +svn co http://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_700/final libcxx +svn co http://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_700/final libcxxabi +svn co http://llvm.org/svn/llvm-project/polly/tags/RELEASE_700/final polly +svn co http://llvm.org/svn/llvm-project/lld/tags/RELEASE_700/final lld +svn co http://llvm.org/svn/llvm-project/openmp/tags/RELEASE_700/final openmp +svn co http://llvm.org/svn/llvm-project/libunwind/tags/RELEASE_700/final libunwind +``` + +编译源码(时间会有些长) +``` +cd ~/llvm_all +mkdir build && cd build +cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DLLVM_BUILD_DOCS=OFF -DCMAKE_INSTALL_PREFIX=/usr/local/clang_7.0.0 ../llvm + +make -j 8 +sudo make install/strip +``` + +将clang加入环境变量 +``` +cd ~ +echo 'export PATH=/usr/local/clang_7.0.0/bin:$PATH' >> ~/.bashrc +echo 'export LD_LIBRARY_PATH=/usr/local/clang_7.0.0/lib:LD_LIBRARY_PATH' >> ~/.bashrc +source ~/.bashrc +``` + +### 编译YouCompleteMe +- 可以使用python +``` +python install.py --clang-completer +``` +- 使用install.sh +``` +./install.sh --clang-completer --system-libclang +``` + +以上二选一 + +``` + +`Vim/Vim插件.md`: + +```md +记录一些常用的好用的Vim插件 + +## 插件管理: +[junegunn/vim-plug](https://github.com/junegunn/vim-plug) + +特点是多线程下载,速度快 + +## 代码补全 +[Valloric/YouCompleteMe](https://github.com/Valloric/YouCompleteMe) + +安装环境: +``` +python 要和 Vim编译选项的版本一致 :version +CMake 最新版本 + +// Windows 下要安装 Visual Studio 15 或 17 +// Linux 下直接运行下面命令即可 +``` +编译命令: +``` +python install.py --clang-completer --msvc 15 +``` +``` + +`Vim/Vim管理多个文件.md`: + +```md +# 文件 + +Vim允许在一个编辑会话中编辑多个文件,既可以每次显示一个文件,也可以把工作区分成若干`分割窗口`或`标签页`,每个`窗口`或`标签页`包含一个独立的`缓冲区`。 + +## 用缓冲区列表管理打开的文件 + +### 文件与缓冲区的区别 + +就像其他编辑器一样,VIM允许读取、编辑文件,并保存修改。在工作过程中,我们通常会说“我们正在编辑一个文件”,但真实情况并不是这样,我们编辑的只是`文件在内存中的映像`,也就是Vim术语中的`缓冲区`。 + +文件是存储在磁盘上的,而缓冲区存在于内存中。当Vim打开一个文件时,该文件的内容被读入一个具有相同名字的缓冲区。 + +### 结识缓冲区列表 + +```shell +➾ $ cd code/files +➾ $ vim *.txt +《 2 files to edit + +➾ :ls +《 1 %a "a.txt" line 1 + 2 "b.txt" line 0 + +➾ :bnext ➾ :ls +《 1 # "a.txt" line 1 + 2 %a "b.txt" line 1 +``` + +`%a`符号指明哪个缓冲区在当前窗口中可见,`#`符号则代表轮换文件。按``可以在当前文件和轮换文件间快速切换。 + +### 使用缓冲区列表 + +`:bprev` 指向移动 +`:bnext` 正向移动 +`:bfirst` 开头 +`:blast` 结尾 + +根据`:ls`显示的编号,可以用`:buffer N`命令直接凭编号跳转到一个缓冲区。 + +也可以更直观的`:buffer {bufname}`格式,`{bufname}`只需包含文件路径中足以唯一标识此缓冲区的字符即可。 + +### 关闭缓冲区 + +`:bdelete N` + +具体操作查看help + +## 用参数列表将缓冲区分组 + +> 参数列表易于管理,适用于一批文件进行分组,使其更容易访问。 + +## 管理隐藏缓冲区 + +> Vim对被修改过的缓冲区会给予特殊对待,以防未加保存就意外退出。 + +当修改a.txt后: +``` +➾ :ls +《 1 %a + "a.txt" line 1 + 2 "b.txt" line 0 +``` +当现在切换缓冲区时: +``` +➾ :bnext 《 E37: No write since last change (add ! to override) +``` +如果强制切换的话使用:`:bnext!` +``` +➾ :bnext! +➾ :ls +《 1 #h + "a.txt" line 1 + 2 %a "b.txt" line 1 +``` + +在退出时,需要对隐藏缓冲区做处理。如果要保留修改,可以执行`:write`命令把缓冲区保存到文件;如果想摒弃此修改,可以执行`:edit!`,重新从磁盘读取此文件,这会用文件的内容覆盖缓冲区中的内容。当缓冲区内容与磁盘文件一致后,可以再次尝试执行`:quit`命令了。 + +如果想退出Vim而不想检查未保存的修改,可以执行`:qall!`命令;如果想保存所有有改动的缓冲区而无需逐个检查,可以用`:wall`命令。 + + + +--- + + +## 工作区分割窗口 + +如果先执 行 `s`,再执行 :edit {filename},就会把工作区分成两个窗 口,并在其中一个窗口中打开新缓冲区,另一个窗口则继续显示原有的 缓冲区。另外一种做法是使用 :split {filename} 命令,它把上述两 步合并成为了一步。 + +|命令|用途| +|---|---| +|\s|水平分割当前窗口,新窗口仍显示当前缓冲区| +|\v|垂直分割当前窗口,新窗口仍显示当前缓冲区| +|:sp[lit] {file}|水平分割当前窗口,新窗口载入{file}| +|:vsp[lit] {file}|垂直分割当前窗口,新窗口载入{file}| + +### 关闭窗口 + +`:close` `c` + +`:only` `o` + +## 用标签页将窗口分组 + +用Vim的标签页可以把工作分隔到不同的工作区。Vim中的标签页更像是Linux中的虚拟桌面,而不是你其他文本编辑器中的标签页。 + +在vim中,标签页是可以容纳一系列窗口的容器(:h **tabpage**) + +**当用edit命令打开一个文件时,Vim却不会自动创建一个新标签页,而是创建一个新缓冲区,并把该缓冲区显示到当前窗口。Vim是用缓冲区列表对打开的文件进行管理的。** + +vim的标签页与缓冲区并非一一对应的关系,相反,应该把标签页想成容纳一系列窗口的窗口。下图**每个标签页都包含一个或多个窗口**。 + +![image](./images/2019-09-24180607.png) + +**把标签页想象成容纳一系列窗口的容器。** + +### 打开及关闭标签页 + +|命令|用途| +|---|---| +|:tabe[eit] {filename}|在新标签页中打开{filename}| +|\T|把当前窗口移到一个新标签页| +|:tabc[lose]|关闭当前标签页及其中的所有窗口| +|:tabo[nly]|只保留活动标签页,关闭所有其他标签页| + +### 在标签页中切换 + +|Ex命令|普通模式命令|用途| +|---|---|---| +|:tabn[ext] {N}|{N}gt|切换到编号为{N}的标签页| +|:tabn[ext]|gt|切换到下一标签页| +|:tabp[revious]|gT|切换到上一标签页| + + +## 缓冲区 + +不带任何参数打开多个文件便可以把它们都放入缓冲区: + +```shell +vim a.txt b.txt +``` + +当使用`:q`关闭文件时,是否看到过`1 more file to edit`的警告?那就是缓冲区的文件。 + +缓冲区之间跳转最常用的方式便是`ctrl+^`(不需要按下shift)来切换当前缓冲区和上一个缓冲区。 + +||| +|--|--|--| +|:ls, :buffers|列出所有缓冲区| +|:bn[ext]|下一个缓冲区| +|:bp[revious]|上一个缓冲区| +|:b {number, expression}|跳转到指定缓冲区| +|:bd, :bd!|删除缓冲区,强制删除| + + +`:b`接受缓冲区编号,或者部分文件名。例如: + +- `:b2`将会跳转到编号为2的缓冲区,如果你正在用`:ls`列出缓冲区,这时只需要输入编号回车即可。 +- `:b exa`将会跳转到最匹配`exa`的文件名,比如`example.html`,模糊匹配撕开文件正是vim缓冲区的强大之处。 + +``` + +`Vim/Vim语法.md`: + +```md +# Vim 语法高亮 + +## 基本语法命令 + +打开语法高亮:`:syntax enable`, 实际上它只是执行如下命令: +``` +:source $VIMRUNTIME/syntax/syntax.vim +``` + +## 语法文件 + +如果该语言是另一个语言的超集,它可以包含那个语言对应的文件。例如,cpp.vim可以包含c.vim文件: +``` +:so $VIMRUNTIME/syntax/c.vim +``` +.vim文件通常使用自动命令载入。例如: +``` +:au Syntax c runtime! syntax/c.vim +:au Syntax cpp runtime! syntax/cpp.vim +``` + +## 命名管理 + +高亮组名用于匹配相同类型事物的高亮项目。它们被链接到用于指定颜色的高亮组。 + +``` + +`Vim/_vimrc`: + +``` +" windows上的配置 + +" [ 基本配置============================================================= + +set nocompatible " be iMproved, required +" filetype off " required +filetype plugin on " preservim/nerdcommenter 需要开启 +set encoding=utf-8 +set enc=utf-8 +set tabstop=4 " 设置制表符长度 +set softtabstop=4 " 设置软制表符 +set shiftwidth=4 " 缩进长度 +set number " 打开行号 +syntax enable "开启语法高亮功能 +syntax on +set t_Co=256 "指定配色方案为256色 +" 需要配合vimrc_example.vim注释一些代码 +set nobackup "表示不需要备份文件 +set noswapfile "表示不创建临时交换文件 +set nowritebackup "表示编辑的时候不需要备份文件 +set noundofile "表示不创建撤销文件 + +" 设置代码折叠为手动 +set foldmethod=manual +"打开文件是默认不折叠代码 +set foldlevelstart=99 + + +" 删除menu bar +set guioptions-=m +" 删除toolbar +set guioptions-=T +" 删除scrollbar +set guioptions-=r +" 删除左边scrollbar +:set guioptions-=L + +" 十字定位线 +set cursorline +set cursorcolumn + +highlight CursorLine cterm=none ctermbg=236 +highlight CursorColumn cterm=none ctermbg=236 + +if has("win32") + source $VIMRUNTIME/delmenu.vim + source $VIMRUNTIME/menu.vim +endif + + +" 启动最大化 +autocmd GUIEnter * simalt ~x + +" 基本配置============================================================= ] + + +" vim-plug: Vim plugin manager +" 下载 https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim 文件到vim的autoload目录 +call plug#begin('$VIMRUNTIME/../plugged') + +" 自动补全 此插件安装的时候需要手动编译 +" https://github.com/ycm-core/YouCompleteMe +Plug 'Valloric/YouCompleteMe' ",{'on':[]} +" augroup load_ycm +" autocmd! +" autocmd InsertEnter * call plug#load('YouCompleteMe') | autocmd! load_cmd +" augroup END + +" nanotech/jellybeans.vim 主题插件 +" 此插件优点是可以重载自定义的高亮 +" https://github.com/nanotech/jellybeans.vim +Plug 'nanotech/jellybeans.vim' + +" jonathanfilip/vim-lucius 主题插件 +" https://github.com/jonathanfilip/vim-lucius +Plug 'jonathanfilip/vim-lucius' + +" cormacrelf/vim-colors-github 主题插件 +" https://github.com/cormacrelf/vim-colors-github +Plug 'cormacrelf/vim-colors-github' + +" tomasiser/vim-code-dark 主题插件 +" https://github.com/tomasiser/vim-code-dark +Plug 'tomasiser/vim-code-dark' + +" dunstontc/vim-vscode-theme 主题插件 +" https://github.com/dunstontc/vim-vscode-theme +Plug 'dunstontc/vim-vscode-theme' + +" altercation/vim-colors-solarized 主题插件 +" https://github.com/altercation/vim-colors-solarized +Plug 'altercation/vim-colors-solarized' + +" luochen1990/rainbow 彩虹括号插件 +" https://github.com/luochen1990/rainbow +Plug 'luochen1990/rainbow' + +" Yggdroot/LeaderF 模糊搜索插件 +" https://github.com/Yggdroot/LeaderF +Plug 'Yggdroot/LeaderF', { 'do': ':LeaderfInstallCExtension' } + +" scrooloose/nerdcommenter 注释插件 +" https://github.com/preservim/nerdcommenter +Plug 'scrooloose/nerdcommenter' + +" babaybus/DoxygenToolkit.vim 函数注释插件 +" https://github.com/babaybus/DoxygenToolkit.vim +Plug 'babaybus/DoxygenToolkit.vim' + +" preservim/nerdtree 目录树插件 +" https://github.com/preservim/nerdtree +Plug 'preservim/nerdtree' " 目录树 + +" vim-airline/vim-airline 状态栏插件 +" https://github.com/vim-airline/vim-airline +Plug 'vim-airline/vim-airline' " 状态栏 +Plug 'vim-airline/vim-airline-themes' " 状态栏主题 + +" tpope/vim-fugitive git插件 +" https://github.com/tpope/vim-fugitive +Plug 'tpope/vim-fugitive' + +" Plug 'plasticboy/vim-markdown' " Vim markdown语法插件 + +" flazz/vim-colorschemes 主题插件 +" https://github.com/flazz/vim-colorschemes +Plug 'flazz/vim-colorschemes' + +" Plug 'hzchirs/vim-material' " material主题 +" Plug 'ludovicchabant/vim-gutentags' " gtags辅助 + +" dense-analysis/ale 动态检查工具 +" https://github.com/dense-analysis/ale +Plug 'dense-analysis/ale' + +call plug#end() + +" 添加 http://github.com/tpope/vim-unimpaired 插件 用来补充一些映射 + +" Vim预置有很多快捷键,再加上各类插件的快捷键,大量快捷键出现在单层空间中难免引起冲突。为缓解该问题,而引入了前缀键。 +" 前缀键默认为“\”。现修改为空格 +let mapleader = "\" + +" [ cormacrelf/vim-colors-github 主题插件 配置============================================================= + +" cormacrelf/vim-colors-github 主题插件 配置 +" Place before the colorscheme github +" use a slightly darker background, like GitHub inline code blocks +" let g:github_colors_soft = 1 +" use the dark theme / light +" set background=dark +" more blocky diff markers in signcolumn (e.g. GitGutter) +" let g:github_colors_block_diffmark = 0 + +" cormacrelf/vim-colors-github 主题插件 配置============================================================= ] + + +" [ 主题及airline配置============================================================= + +" colorscheme molokai +" colorscheme desert +" set background=light +" colorscheme vim-material +" colorscheme wombat +" colorscheme jellybeans " 来自nanotech/jellybeans.vim +" colorscheme lucius " 来自jonathanfilip/vim-lucius +colorscheme molokai " 来自flazz/vim-colorschemes 主题插件 + +let g:airline_theme='powerlineish' + +" colorscheme github " 来自cormacrelf/vim-colors-github +" let g:airline_theme='github' " vim-colors-github 主题提供 + +" colorscheme codedark " 来自tomasiser/vim-code-dark +" let g:airline_theme = 'codedark' " 来自tomasiser/vim-code-dark + +" colorscheme dark_plus " 来自dunstontc/vim-vscode-theme +" let g:airline_theme = 'dark_plus' " 来自dunstontc/vim-vscode-theme + +" 主题及airline配置============================================================= ] + + +" jonathanfilip/vim-lucius 主题插件 配置 +" LuciusBlackHighContrast + + +" nanotech/jellybeans.vim 主题插件 配置 +" set guifont=Monaco:h10 noanti +" let g:jellybeans_use_gui_italics = 0 " 支持斜体 + + +" altercation/vim-colors-solarized 主题插件 配置 +" 蓝色的,没想象中的好看 +" set background=dark +" colorscheme solarized + + +" luochen1990/rainbow 彩虹括号插件 配置 +" 自动启用 +let g:rainbow_active = 1 + + +" [ dense-analysis/ale 动态检查工具 配置============================================================= + +" dense-analysis/ale 动态检查工具 配置 +" 具体配置请参考: https://github.com/dense-analysis/ale/tree/master/doc +" 抄自 https://skywind3000.com/blog/archives/2084/ + +" 使用clang对c和c++进行语法检查,对python使用pylint进行语法检查 +" 设置对应语言的linter的时候,请确认安装了 +" c和c++环境下面是用的clang,需要安装clang或gcc,配置到环境变量 +" 在安装YCM后,clangd也被安装,如果配置到环境变量后,clangd的lsp +" 也会被启用。 +let g:ale_linters = { +\ 'c++': ['clang'], +\ 'c': ['clang'], +\ 'python': ['pylint'], +\} + +" 不希望ale运行除我明确要求的内容以外的其他内容 +" 感觉好像没有生效,但目前不影响其他插件,先这么放着吧 +" let g:ale_linters_explicit = 1 +"始终开启标志列 +let g:ale_sign_column_always = 1 +let g:ale_set_highlights = 1 +"自定义error和warning图标 +let g:ale_sign_error = '✗' +let g:ale_sign_warning = '⚡' +"在vim自带的状态栏中整合ale +let g:ale_statusline_format = ['✗ %d', '⚡ %d', '✔ OK'] +"显示Linter名称,出错或警告等相关信息 +let g:ale_echo_msg_error_str = 'E' +let g:ale_echo_msg_warning_str = 'W' + + +" let g:ale_echo_msg_format = '[%linter%] %s [%severity%]' + +let g:ale_completion_delay = 500 +let g:ale_echo_delay = 20 +let g:ale_lint_delay = 500 +" 报错信息格式 +let g:ale_echo_msg_format = '[%linter%] %code: %%s' +" 以下两句规定了如果在normal模式下文件改变及以及离开insert模式才运行linter +" 这是相对保守的做法,如果没有的话,会导致YCM的补全框频繁的刷新 +let g:ale_lint_on_text_changed = 'normal' +let g:ale_lint_on_insert_leave = 1 + +" 我把YCM的错误检测提示标志关了,不然这两个哪个在后面哪个生效 +" let g:airline#extensions#ale#enabled = 1 +" 这里就是传给编译器的参数 +" 这里需要根据每个项目的不同去修改它的引用头文件及宏定义 + +" CentOS6.10内核编译参数 +" 暂时不知道怎么换行 +let g:ale_c_cc_options = '' + +" let g:ale_cpp_cc_options = '-Wall -std=c++11' +" check用人家默认的就可以了 '--enable=style' 不知道是什么 +" let g:ale_c_cppcheck_options = '' +" let g:ale_cpp_cppcheck_options = '' + +hi! clear SpellBad +hi! clear SpellCap +hi! clear SpellRare +hi! SpellBad gui=undercurl guisp=red +hi! SpellCap gui=undercurl guisp=blue +hi! SpellRare gui=undercurl guisp=magenta + +" 查看报错信息 +noremap d :ALEDetail + + +" 创建一个函数用来选择cc_options +function! SelectCcOptions() + +let g:CcOptionsCollection = [ +\ {'2.6.32-754' : '-I. -std=c99 +\ -IS:\\usr\\src\\kernels\\2.6.32-754.el6.x86_64\\include +\ -IS:\\usr\\src\\kernels\\2.6.32-754.el6.x86_64\\arch\\x86\\include +\ -DMODULE -D__KERNEL__ -D__GNUC__=4 -DCONFIG_64BIT',}, +\ {'UOSV21-5.4.50' : '-I. -std=c99 +\ -IQ:\\usr\\src\\linux-headers-5.4.50-amd64-desktop\\include +\ -IQ:\\usr\\src\\linux-headers-5.4.50-amd64-desktop\\arch\\x86\\include +\ -DMODULE -D__KERNEL__ -D__GNUC__=8 -DCONFIG_64BIT'}, +\ {'Windows Driver Kits v8.1' : '-I. -std=c99 +\ -IE:\\WindowsKits\\8.1\\Include\\km\\crt +\ -IE:\\WindowsKits\\8.1\\Include\\km +\ -IE:\\WindowsKits\\8.1\\Include\\shared +\ -D_WIN64 -D_AMD64_ -DAMD64 -DDEPRECATE_DDK_FUNCTIONS=1 -DMSC_NOOPT +\ -D_WIN32_WINNT=0x0601 -DWINVER=0x0601 -DWINNT=1 -DNTDDI_VERSION=0x06010000'}, +\ ] + + " echo len(g:CcOptionsCollection) + + let i = 0 + let selectList = [] + while i < len(g:CcOptionsCollection) + " echo i + let tempDict = get(g:CcOptionsCollection, i, "") + echo string(i) . ":" . string(keys(tempDict)) + let i += 1 + call add(selectList, string(keys(tempDict))) + endwhile + + echo "Input Your Select:" + + " let userSelect = inputlist(selectList) + " echo userSelect + + let userInput = getchar() + echo nr2char(userInput) + + let tempDict = get(g:CcOptionsCollection, nr2char(userInput), {"NONE" : ""}) + + if keys(tempDict)[0] == "NONE" + let g:CurrentCcOptions = '' + else + let g:CurrentCcOptions = values(tempDict)[0] + endif + + echo g:CurrentCcOptions + + " echo g:CcOptionsCollection + + let g:ale_c_cc_options = g:CurrentCcOptions + + " 重新载入打开的文件 + bufdo e + +endfunction + +function! GetCcOptions() + echo g:ale_c_cc_options +endfunction + +" ale select sdk +noremap ss : call SelectCcOptions() + +" ale show current sdk +noremap gs : call GetCcOptions() + +" dense-analysis/ale 动态检查工具 配置============================================================= ] + + + +" [ scrooloose/nerdcommenter 注释插件 配置============================================================= + +" scrooloose/nerdcommenter 注释插件 配置 +" [count]cc |NERDCommenterComment| +" Comment out the current line or text selected in visual mode. +" +" [count]cn |NERDCommenterNested| +" Same as cc but forces nesting. +" +" [count]c |NERDCommenterToggle| +" Toggles the comment state of the selected line(s). If the topmost selected line is commented, all selected lines are uncommented and vice versa. +" +" [count]cm |NERDCommenterMinimal| +" Comments the given lines using only one set of multipart delimiters. +" +" [count]ci |NERDCommenterInvert| +" Toggles the comment state of the selected line(s) individually. +" +" [count]cs |NERDCommenterSexy| +" Comments out the selected lines with a pretty block formatted layout. +" +" [count]cy |NERDCommenterYank| +" Same as cc except that the commented line(s) are yanked first. + +" Create default mappings +let g:NERDCreateDefaultMappings = 1 + +" Add spaces after comment delimiters by default +let g:NERDSpaceDelims = 1 + +" Use compact syntax for prettified multi-line comments +let g:NERDCompactSexyComs = 1 + +" Align line-wise comment delimiters flush left instead of following code indentation +let g:NERDDefaultAlign = 'left' + +" Set a language to use its alternate delimiters by default +let g:NERDAltDelims_java = 1 + +" Add your own custom formats or override the defaults +let g:NERDCustomDelimiters = { 'c': { 'left': '/**','right': '*/' } } + +" Allow commenting and inverting empty lines (useful when commenting a region) +let g:NERDCommentEmptyLines = 1 + +" Enable trimming of trailing whitespace when uncommenting +let g:NERDTrimTrailingWhitespace = 1 + +" Enable NERDCommenterToggle to check all selected lines is commented or not +let g:NERDToggleCheckAllLines = 1 + +" scrooloose/nerdcommenter 注释插件 配置============================================================= ] + + + +" [ babaybus/DoxygenToolkit.vim 函数注释插件 配置============================================================= +let g:DoxygenToolkit_briefTag_funcName = "yes" +" for C++ style, change the '@' to '\' +" default C++ comments are : /** ... */. But if you prefer to use /// +" Doxygen comments just add 'let g:DoxygenToolkit_commentType = "C++"' +" let g:DoxygenToolkit_commentType = "C++" +let g:DoxygenToolkit_briefTag_pre = "\\brief " +let g:DoxygenToolkit_templateParamTag_pre = "\\tparam " +let g:DoxygenToolkit_paramTag_pre = "\\param " +let g:DoxygenToolkit_returnTag = "\\return " +let g:DoxygenToolkit_throwTag_pre = "\\throw " " @exception is also valid +let g:DoxygenToolkit_fileTag = "\\file " +let g:DoxygenToolkit_dateTag = "\\date " +let g:DoxygenToolkit_authorTag = "\\author " +let g:DoxygenToolkit_versionTag = "\\version " +let g:DoxygenToolkit_blockTag = "\\name " +let g:DoxygenToolkit_classTag = "\\class " +let g:DoxygenToolkit_authorName = "Heming Cheng, supermanc88@gmail.com" +let g:doxygen_enhanced_color = 1 +" 不生成多余的换行 +let g:DoxygenToolkit_compactDoc = "yes" +" babaybus/DoxygenToolkit.vim 函数注释插件 配置============================================================= ] + + + +" [ preservim/nerdtree 目录树插件 配置============================================================= +noremap :NERDTreeToggle " F5 开关目录树 +" open NERDTree automatically when Vim starts +" Start NERDTree and put the cursor back in the other window. +autocmd VimEnter * NERDTree | wincmd p +" preservim/nerdtree 目录树插件 配置============================================================= ] + + + +" [ YouCompleteMe 配置============================================================= +"You Complete Me 编译 +"需要安装vim对应版本的python,查看方式为: :version +"安装 VS2017 或者 VS2015 +"下载YCM插件源码后,如下命令编译插件: +"python install.py --clang-completer --msvc 16 + +"此处是全局配置文件路径 +let g:ycm_global_ycm_extra_conf = '$VIMRUNTIME/../.ycm_extra_conf.py' +"关闭每次导入配置文件前的询问 +let g:ycm_confirm_extra_conf = 0 +" "let g:syntastic_always_populate_loc_list = 1 "方便使用syntastic进行语法检查 +"开启语法关键字补全 +let g:ycm_seed_identifiers_with_syntax=1 +"关闭补全预览 +let g:ycm_add_preview_to_completeopt=0 +let g:ycm_seed_identifiers_with_syntax = 1 "标识符种子 +let g:ycm_min_num_of_chars_for_completion = 1 "当按一个字符之后开始提示 +let g:ycm_semantic_triggers = { + \ 'c,cpp,python,java,go,erlang,perl': ['re!\w{2}'], + \ 'cs,lua,javascript': ['re!\w{2}'], + \ } "语义补全 +" 补全内容不以分割子窗口形式出现,只显示补全列表 +set completeopt-=preview +"修改提示框颜色 +highlight PMenu ctermfg=0 ctermbg=242 guifg=black guibg=darkgrey +highlight PMenuSel ctermfg=242 ctermbg=8 guifg=darkgrey guibg=black +" 为了使用ale语法检查,要把ycm的语法检查关闭 +"disable ycm 语法检查 +" 这里关闭之后,下面的错误提示也就没用了" +let g:ycm_enable_diagnostic_signs = 0 +let g:ycm_enable_diagnostic_highlighting = 0 +" 错误标记 +let g:ycm_error_symbol = '✗' "set error or warning signs +" warning标记 +let g:ycm_warning_symbol = '⚡' "⚠ + +" "highlight YcmErrorSign 标记颜色 +" "highlight YcmWarningSign ctermbg=none +" "highlight YcmErrorSection 代码中出错字段颜色 +" highlight YcmWarningSection ctermbg=none +" "highlight YcmErrorLine 出错行颜色 +" "highlight YcmWarningLine + +" " 跳转快捷键 +nnoremap :YcmCompleter GoToDeclaration| +nnoremap :YcmCompleter GoToDefinition| +nnoremap :YcmCompleter GoToDefinitionElseDeclaration| +" YouCompleteMe 配置============================================================= ] + + + +" source $VIMRUNTIME/cscope_maps.vim +" +" " gutentags 配置======================================================== +" " gutentags 搜索工程目录的标志,当前文件路径向上递归直到碰到这些文件/目录名 +" let g:gutentags_project_root = ['.root', '.svn', '.git', '.hg', '.project'] +" +" " 所生成的数据文件的名称 +" let g:gutentags_ctags_tagfile = '.tags' +" +" " 同时开启 ctags 和 gtags 支持: +" let g:gutentags_modules = [] +" if executable('ctags') +" let g:gutentags_modules += ['ctags'] +" endif +" if executable('gtags-cscope') && executable('gtags') +" let g:gutentags_modules += ['gtags_cscope'] +" endif +" +" " 将自动生成的 ctags/gtags 文件全部放入 ~/.cache/tags 目录中,避免污染工程目录 +" let g:gutentags_cache_dir = expand('~/.cache/tags') +" +" " 配置 ctags 的参数 +" let g:gutentags_ctags_extra_args = ['--fields=+niazS', '--extra=+q'] +" let g:gutentags_ctags_extra_args += ['--c++-kinds=+px'] +" let g:gutentags_ctags_extra_args += ['--c-kinds=+px'] +" +" " 如果使用 universal ctags 需要增加下面一行 +" let g:gutentags_ctags_extra_args += ['--output-format=e-ctags'] +" +" " 禁用 gutentags 自动加载 gtags 数据库的行为 +" let g:gutentags_auto_add_gtags_cscope = 0 +" " gutentags 配置======================================================== +" +" " Gtags 配置======================================================== +" let $GTAGSLABEL = 'native-pygments' +" let $GTAGSCONF = 'D:/Program Files (x86)/Vim/extends/gtags/share/gtags/gtags.conf' +" " cscope +" set cscopetag " 使用 cscope 作为 tags 命令 +" set cscopeprg=gtags-cscope " 使用 gtags-cscope 代替 cscope +" +" " gtags +" let GtagsCscope_Auto_Load = 1 +" let CtagsCscope_Auto_Map = 1 +" let GtagsCscope_Quiet = 1 +" " Gtags 配置======================================================== + + + +" [ Yggdroot/LeaderF 模糊搜索插件 配置============================================================= + +" 此插件需要 GNU GLOBAL(gtags) 的配合 +" Windows下载地址 https://www.gnu.org/software/global/download.html +" 正则表达式/字符串检索 Leaderf rg ... +" ripgrep下载地址 https://github.com/BurntSushi/ripgrep/releases +" 需要安装ctags +" 下载地址 https://github.com/universal-ctags/ctags-win32/releases +" 以上安装后,均需要加入到环境变量中 +" 以下是默认的配置 +" don't show the help in normal mode +let g:Lf_HideHelp = 1 +let g:Lf_UseCache = 0 +let g:Lf_UseVersionControlTool = 0 +let g:Lf_IgnoreCurrentBufferName = 1 + +" 气泡模式,就是在窗口中央弹出一个窗口 +" popup mode +let g:Lf_WindowPosition = 'popup' +let g:Lf_PreviewInPopup = 1 +" let g:Lf_StlSeparator = { 'left': "\ue0b0", 'right': "\ue0b2", 'font': "DejaVu Sans Mono for Powerline" } +" let g:Lf_PreviewResult = {'Function': 0, 'BufTag': 0 } + +" 使用 ctrl + p 查找文件 +let g:Lf_ShortcutF = '' + +" 键盘映射 +" 由于leaderf映射了 ctrl-f和ctrl-b,所以上下翻页使用ctrl-d和ctrl-u +" noremap 可在 普通、可视、选择、操作符等待 下工作 +" CTRL-U 使窗口在缓冲区中向上滚动。滚动的行数由 'scroll' 选项指定 (默认设置为半个屏幕) +" CTRL-R 重做 [count] 次被撤销的更改。 +noremap fb :=printf("Leaderf buffer %s", "") +noremap fm :=printf("Leaderf mru %s", "") +noremap ft :=printf("Leaderf bufTag %s", "") +noremap fl :=printf("Leaderf line %s", "") + +noremap :=printf("Leaderf! rg --current-buffer -e %s ", expand("")) + +" 使用rg进行查找 +" expand("") 是取当前光标下的单词 +noremap :=printf("Leaderf! rg -e %s ", expand("")) +" search visually selected text literally +xnoremap gf :=printf("Leaderf! rg -F -e %s ", leaderf#Rg#visual()) +noremap go :Leaderf! rg --recall + +" gtags 数据库文件存储在$HOME/.LfCache/gtags/%PATH%OF%YOUR%PROJECT/ +" should use `Leaderf gtags --update` first +" gtags是否自动生成 +" 当代码有更改并且已经有 gtags 数据库生成时,更改的代码会自动同步到 gtags 数据库(即使g:Lf_GtagsAutoGenerate是0) +" 只有在项目根目录下有g:Lf_RootMarkers(默认值是['.git', '.hg', '.svn'])里面指定的文件或目录时,LeaderF 才会自动生成 gtags 数据库; +" 否则只能手动生成 gtags 数据库:Leaderf gtags --update,但是当代码有更改时,gtags 数据库依然可以自动更新。 +" 用来指定 gtags.conf 文件的路径,一般情况下不需要指定,默认值就可以很好地工作。 +" 对于Windows上,如果相对于gtags.exe所在路径有../share/gtags/gtags.conf,也不需要指定该选项。 +" 如果需要用户自己特有的针对 gtags 的配置,可以指定用户的配置文件。 +" 也可以在vimrc里设置g:Lf_Gtagsconf达到同样的目的 +" 需要把 gtags.exe的所在路径加入环境变量 D:\Vim\plugged\glo665wb\bin +" 错误信息查看 :mess +" 当出现 gtags seems corrupted 时,可能是tags生成目录有文件,先删除了,再重新生成 +let g:Lf_GtagsAutoGenerate = 1 +" let g:Lf_Gtagsconf = '$VIMRUNTIME/../plugged/glo665wb/share/gtags/gtags.conf' + +" 开启此功能时,需要python安装以下模块 +" pip install pygments +let g:Lf_Gtagslabel = 'native-pygments' + +" 查找引用 +noremap fr :=printf("Leaderf! gtags -r %s --auto-jump", expand("")) +" 查找定义 +noremap fd :=printf("Leaderf! gtags -d %s --auto-jump", expand("")) +" 再次调用刚才的命令 +noremap fo :=printf("Leaderf! gtags --recall %s", "") +" 跳转到下一个tag +noremap fn :=printf("Leaderf gtags --next %s", "") +" 跳转到上一个tag +noremap fp :=printf("Leaderf gtags --previous %s", "") + +" F2显示出当前文件的函数列表 +noremap :LeaderfFunction! + +" Yggdroot/LeaderF 模糊搜索插件 配置============================================================= ] + + +" [ vim-airline/vim-airline 状态栏插件 配置============================================================= +" vim-airline/vim-airline 状态栏插件 配置 +" 默认只有下面的状态栏 +" 在上面显示buffer/tab相关状态栏 smart tab line 开启 +let g:airline#extensions#tabline#enabled = 1 + +" 默认显示的是垂直分隔的 +" 这里是否生效还要通过下面的安装字体才可以 +" 要使用箭头样式需要打开powerline +" 一般使用Source Code Pro +let g:airline_powerline_fonts = 1 + +" 自定义字符 +" if !exists('g:airline_symbols') +" let g:airline_symbols = {} +" endif +" +" " unicode symbols +" let g:airline_left_sep = '»' +" let g:airline_left_sep = '▶' +" let g:airline_right_sep = '«' +" let g:airline_right_sep = '◀' +" let g:airline_symbols.crypt = '🔒' +" let g:airline_symbols.linenr = '☰' +" let g:airline_symbols.linenr = '␊' +" let g:airline_symbols.linenr = '␤' +" let g:airline_symbols.linenr = '¶' +" let g:airline_symbols.maxlinenr = '' +" let g:airline_symbols.maxlinenr = '㏑' +" let g:airline_symbols.branch = '⎇' +" let g:airline_symbols.paste = 'ρ' +" let g:airline_symbols.paste = 'Þ' +" let g:airline_symbols.paste = '∥' +" let g:airline_symbols.spell = 'Ꞩ' +" let g:airline_symbols.notexists = 'Ɇ' +" let g:airline_symbols.whitespace = 'Ξ' +" vim-airline/vim-airline 状态栏插件 配置============================================================= ] + + +" [ 自定义功能配置============================================================= + +" 打开自己写的常用的vim快捷键 +let g:helpTxtOpened = 0 +function OpenMyHelpTxtToggle() +" echo g:helpTxtOpened +" let g:helpTxtOpened = bufexists('C:\\Users\\CHM\\OneDrive\\工作\\常用\\vim快捷键.txt') +" let g:helpTxtOpened = execute "ls" +" echo g:helpTxtOpened + if g:helpTxtOpened == 1 + :bd C:\\Users\\CHM\\OneDrive\\工作\\常用\\vim快捷键.txt + let g:helpTxtOpened = 0 + else + :vsp C:\\Users\\CHM\\OneDrive\\工作\\常用\\vim快捷键.txt + execute "normal \\" + :vertical resize -20 + let g:helpTxtOpened = 1 + endif +endfunction +" noremap :call OpenMyHelpTxt():vertical resize -20 +noremap :call OpenMyHelpTxtToggle() + +" 自定义功能配置============================================================= ] + +" ==================================================================== +" 下载字体 +" https://github.com/adobe-fonts/source-code-pro/releases +" 设置字体 +if has("gui_running") + if has("gui_gtk2") + set guifont=Inconsolata\ 12 + elseif has("gui_macvim") + set guifont=Menlo\ Regular:h14 + elseif has("gui_win32") + " set guifont=Consolas:h12:cANSI + set guifont=Source\ Code\ Pro:h11:cANSI + endif +endif +" " ==================================================================== + + + +" " 窗口透明度 配置======================================================== +" if has('gui_running') && has('libcall') +" let g:MyVimLib = $VIMRUNTIME.'/gvimfullscreen.dll' +" function ToggleFullScreen() +" call libcallnr(g:MyVimLib, "ToggleFullScreen", 0) +" endfunction +" +" "Alt+Enter +" map :call ToggleFullScreen() +" +" let g:VimAlpha = 240 +" function! SetAlpha(alpha) +" let g:VimAlpha = g:VimAlpha + a:alpha +" if g:VimAlpha < 180 +" let g:VimAlpha = 180 +" endif +" if g:VimAlpha > 255 +" let g:VimAlpha = 255 +" endif +" call libcall(g:MyVimLib, 'SetAlpha', g:VimAlpha) +" endfunction +" +" "Shift+Y +" nmap :call SetAlpha(3) +" "Shift+T +" nmap :call SetAlpha(-3) +" +" function! SwitchVimAlpha() +" if g:VimAlpha != 180 +" let g:VimAlpha = 180 +" else +" let g:VimAlpha = 255 +" endif +" call libcall(g:MyVimLib, 'SetAlpha', g:VimAlpha) +" endfunction +" " F8 +" map :call SwitchVimAlpha() +" +" let g:VimTopMost = 0 +" function! SwitchVimTopMostMode() +" if g:VimTopMost == 0 +" let g:VimTopMost = 1 +" else +" let g:VimTopMost = 0 +" endif +" call libcall(g:MyVimLib, 'EnableTopMost', g:VimTopMost) +" endfunction +" +" "Shift+R +" nmap :call SwitchVimTopMostMode() +" endif + +" 窗口透明度 配置======================================================== + + + + + +``` + +`Vim/readme.txt`: + +```txt +vimrc文件可用在Linux和Windows系统上 + +在Linux上使用不用修改配置 + + +在Windows上使用时,需要把配置中的路径改成绝对路径,不可使用~ + +``` + +`Vim/vimrc`: + +``` +" Linux Vim Config +" +" by supermanc88 +" Update 2020-02-26 +" +" 基本配置====================================================================================================================== +set nocompatible " be iMproved, required +filetype off " required + +set encoding=utf-8 + +set tabstop=4 " 设置制表符长度 +set softtabstop=4 " 设置软制表符 +set shiftwidth=4 " 缩进长度 + +set number " 打开行号 + +syntax enable "开启语法高亮功能 +syntax on + +set t_Co=256 "指定配色方案为256色 +" 设置代码折叠为手动 +set foldmethod=manual +"打开文件是默认不折叠代码 +set foldlevelstart=99 +" 基本配置======================================================================================================================= + + + +" Plug Manager=================================================================================================================== +" vim-plug: Vim plugin manager +call plug#begin('~/.vim/plugged') +" 自动补全 此插件安装的时候需要手动编译 +Plug 'Valloric/YouCompleteMe' ",{'on':[]} +" augroup load_ycm +" autocmd! +" autocmd InsertEnter * call plug#load('YouCompleteMe') | autocmd! load_cmd +" augroup END +Plug 'scrooloose/nerdtree' " 目录树 +"Plug 'jistr/vim-nerdtree-tabs' " 共享一个目录树 +"Plug 'Xuyuanp/nerdtree-git-plugin' " 在nerdtree中显示git的信息 +Plug 'vim-airline/vim-airline' " 状态栏 +Plug 'vim-airline/vim-airline-themes' " 状态栏主题 +" Plug 'plasticboy/vim-markdown' " Vim markdown语法插件 +Plug 'flazz/vim-colorschemes' " 代码高亮, 主题 +Plug 'hzchirs/vim-material' " material主题 +Plug 'Yggdroot/LeaderF', { 'do': '.\install.bat' } " 搜索插件 +Plug 'supermanc88/gtags' " gtags +" Plug 'ludovicchabant/vim-gutentags' " gtags辅助 +Plug 'ronakg/quickr-preview.vim' " quickfix 窗口预览 +call plug#end() +" Plug Manager=================================================================================================================== + + +" scheme 配置==================================================================================================================== +colorscheme darkblack +" colorscheme molokai +" colorscheme desert +" set background=light +"colorscheme vim-material +" colorscheme wombat +" scheme 配置==================================================================================================================== + + + +" nerdtree 配置================================================================================================================== +" nnoremap :NERDTree " F5 开启 +nnoremap :NERDTreeToggle " F5 开启/关闭 +autocmd vimenter * if !argc()|NERDTree|endif " 打开vim时如果没有文件自动打开NERDTree + +" nerdtree-git-plugin 配置 +" let g:NERDTreeIndicatorMapCustom = { +" \ "Modified" : "✹", +" \ "Staged" : "✚", +" \ "Untracked" : "✭", +" \ "Renamed" : "➜", +" \ "Unmerged" : "═", +" \ "Deleted" : "✖", +" \ "Dirty" : "✗", +" \ "Clean" : "✔︎", +" \ 'Ignored' : '☒', +" \ "Unknown" : "?" +" \ } +" nerdtree 配置================================================================================================================== + + + +" YouCompleteMe 配置============================================================================================================= +let g:ycm_global_ycm_extra_conf = '~/.vim/.ycm_extra_conf.py' "此处是全局配置文件路径 +let g:ycm_confirm_extra_conf = 0 "关闭每次导入配置文件前的询问 +"let g:syntastic_always_populate_loc_list = 1 "方便使用syntastic进行语法检查 +let g:ycm_seed_identifiers_with_syntax=1 "开启语法关键字补全 +let g:ycm_add_preview_to_completeopt=0 "关闭补全预览 +let g:ycm_semantic_triggers = { + \ 'c,cpp,python,java,go,erlang,perl': ['re!\w{2}'], + \ 'cs,lua,javascript': ['re!\w{2}'], + \ } "语义补全 +" 补全内容不以分割子窗口形式出现,只显示补全列表 +set completeopt-=preview +" "修改提示框颜色 +highlight Pmenu ctermfg=0 ctermbg=242 guifg=black guibg=darkgrey +highlight PmenuSel ctermfg=242 ctermbg=8 guifg=darkgrey guibg=black +" 错误标记 +let g:ycm_error_symbol = '?' "set error or warning signs + +" warning标记 +let g:ycm_warning_symbol = '?' + +"highlight YcmErrorSign 标记颜色 +"highlight YcmWarningSign ctermbg=none +"highlight YcmErrorSection 代码中出错字段颜色 +highlight YcmWarningSection ctermbg=none +"highlight YcmErrorLine 出错行颜色 +"highlight YcmWarningLine + +" 跳转快捷键 +nnoremap :YcmCompleter GoToDeclaration| +nnoremap :YcmCompleter GoToDefinition| +nnoremap :YcmCompleter GoToDefinitionElseDeclaration| +" YouCompleteMe 配置============================================================================================================ + + + +" Gtags 配置==================================================================================================================== +" 用来告诉gtags默认C/C++等原生支持的代码直接使用gtags本地分析器,其他语言使用pygments模块 +let $GTAGSLABEL = 'native-pygments' +" 这个必须设置,否则会找不到 native-pygments和 language map 的定义 +let $GTAGSCONF = '/home/superman/.vim/plugged/gtags/gtags.conf' +" cscope +set cscopetag " 使用 cscope 作为 tags 命令 +set cscopeprg=gtags-cscope " 使用 gtags-cscope 代替 cscope +" gtags +let GtagsCscope_Auto_Load = 1 +let CtagsCscope_Auto_Map = 1 +let GtagsCscope_Quiet = 1 +" Gtags 配置=================================================================================================================== + + + +" LeaderF 配置================================================================================================================= +noremap :LeaderfFunction! +noremap :Leaderf gtags +let g:Lf_Gtagsconf = '/home/superman/.vim/plugged/gtags/gtags.conf' +let g:Lf_GtagsAutoGenerate = 1 +let g:Lf_Gtagslabel = 'native-pygments' +noremap fr :=printf("Leaderf! gtags -r %s --auto-jump", expand("")) +noremap fd :=printf("Leaderf! gtags -d %s --auto-jump", expand("")) +noremap fo :=printf("Leaderf! gtags --recall %s", "") +noremap fn :=printf("Leaderf gtags --next %s", "") +noremap fp :=printf("Leaderf gtags --previous %s", "") +" LeaderF 配置================================================================================================================= + + + + +" airline theme 配置=========================================================================================================== +let g:airline_theme='bubblegum' " airline 主题 +"let g:airline_theme='molokai' " airline 主题 +let g:airline#extensions#tabline#enabled = 1 " smart tab line 开启 +"let g:airline_theme='material' +" airline theme 配置=========================================================================================================== + + + + + +" Linux 透明度 配置=========================================================================================================== +hi Normal ctermfg=252 ctermbg=none +" Linux 透明度 配置=========================================================================================================== + +``` + +`Vim/vim插件之Leaderf.md`: + +```md +## 安装global + +### 卸载原有global + +```shell +sudo apt remove global +``` + +## Leaderf 使用 global功能 + +```shell +:LeaderfRgRecall 显示上次 rg 的结果 +:Leaderf rg 实时检索 +:Leaderf rg [option] 后面的 [option] 和 rg 的语法保持一致 +``` + +### 安装最新 + +需要安装以下信赖库: + +```shell +sudo apt-get install libncurses5-dev libncursesw5-dev +``` + +在[GNU官网](https://www.gnu.org/software/global/download.html)下载最新源码。 + +```shell +wget http://tamacom.com/global/global-6.6.3.tar.gz + +tar -zvxf global-6.6.3.tar.gz + +cd global-6.6.3 + +./configure + +make && make install +``` + +## 安装ripgrep + +[github](https://github.com/BurntSushi/ripgrep) + +Ubuntu18以下需要下载安装包: + +```shell +curl -LO https://github.com/BurntSushi/ripgrep/releases/download/11.0.2/ripgrep_11.0.2_amd64.deb + +sudo dpkg -i ripgrep_11.0.2_amd64.deb +``` + +## 快捷键 + +```shell +, : 退出 LeaderF. + : 在模糊匹配和正则式匹配之间切换 + : 在全路径搜索和名字搜索之间切换 + : 在检索模式和选择模式之间切换 +, : 在结果列表里选择 + : 在水平窗口打开 + : 在垂直窗口打开 + : 在新标签打开 + : 预览结果 +``` + +``` + +`Vim/vim查看和使用历史命令.md`: + +```md +# vim查看和使用历史命令 + + + +1. 用`q:`可以查看最近的历史命令的命令行窗口 +2. \和\,也可查看并用Enter使用这个命令 +3. `:history`查看所有命令行模式下输入的命令历史 + + + +## 如何再执行上一个(历史)命令 + +输入命令提示符( : )+ 上下箭头(键) +``` + +`Vim/光标移动.md`: + +```md +# 光标移动 + +## 上下左右 + +![img](images/光标移动/v2-b071d0e288557777031d9c113d654986_720w.jpg) + +| H | J | K | L | +| ---- | ---- | ---- | ---- | +| 左 | 下 | 上 | 右 | + + + +## 基于单词的移动 + +![img](images/光标移动/v2-c37a245317ca494ce6122ac92a69a3bd_720w.jpg) + +| w(word) | b(back) | e(end) | ge | +| -------------- | -------------- | -------------- | -------------- | +| 下一个单词开头 | 上一个单词开头 | 下一个单词结尾 | 上一个单词结尾 | + + + +## 基于行移动 + +### 行间跳转 + +| gg | G | :22\ | +| ------------ | ------------ | ----------- | +| 跳到全文首行 | 跳到全文末尾 | 跳转到22行 | + + + +### 开头和结尾 + +| 0 | $ | +| ---------- | ---------- | +| 移动到行首 | 移动到行尾 | + + + +### 行内定位 + +![img](images/光标移动/v2-fe71b999b882db069dfb8714f9455b88_720w.jpg) + +| f\ | t\ | +| --------------------------------------- | ------------------------------------------------- | +| 移动到当前行的第一个char字母处,f即find | 移动到当前行的第一个char字母的前一个字母上,t即to | + +> F\和T\即上面的反向查找 + +| 分号`;` | 逗号`,` | +| -------------------------------------------- | -------------------------------------------- | +| 移动到下一处该字符出现的位置,相当于重复f和t | 移动到上一处该字符出现的位置,相当于重复F和T | + + + +### 基于查找 + +| /word\ | ?word\ | +| ------------------------------------------------------------ | ------------- | +| 从光标向后查找word单词,将光标移动到第一个word单词的首字母w处 | 向前查找 | + + + +### 匹配括号间跳转 + +`%` 从当前选中的括号处,跳到对应的另一个括号位置 + +**``** 在最近的两次跳转中来回跳 + + + +### 跳转 + +vim会记录跳转前后的位置,并提供命令让我们原路返回 + +- ctrl + o 跳转到上次跳转的位置 +- ctrl + i 和\相反 +``` + +`Vim/在ubuntu下安装最新vim.md`: + +```md +在默认的情况下Linux安装vim基本上都是7.x版本 + +To install Vim 8.1.0374 on Ubuntu (16.04, 18.04) and Linux Mint : +Adding this PPA to your system +You can update your system with unsupported packages from this untrusted PPA by adding ppa:jonathonf/vim to your system's Software Sources. + + +``` +sudo add-apt-repository ppa:jonathonf/vim +sudo apt-get update +sudo apt-get install vim +``` + +``` + +`Vim/基于单词的移动.md`: + +```md +|命令|光标动作|助记| +|---|---|---| +|w|正向移动到下一单词开头|(for-)word| +|b|反向移动到当前单词/上一单词开头|back-word| +|e|正向移动到当前单词/下一单词结尾|end| +|ge|反向移动到上一单词结尾|go-end| + +``` + +`Vim/查找与替换substitute.md`: + +```md +# 查找与替换 (substitute) + + + +```shell +:help substitute + +:[range]s[ubstitute]/{pattern}/{string}/[flags] [count] +``` + + + +常用flag: + +[c] :确认每个替换项,vim会高亮出每个匹配的string + +\[g]:替换所有匹配项 +``` + +`Vim/行的复制和移动.md`: + +```md +# 行的复制和移动 + +# :copy + +`:copy`命令简写为`:t`,可以把一部分代码复制到另一部分。 + +```shell +:[range]copy {addreee} +``` + +# :move + +`:move`命令简写为`:m`,可以把一行或多行文档移动到其他地方。 + +```shell +:[range]move {address} +``` +``` + +`Windbg/Windbg常用命令.md`: + +```md +# Windbg常用命令 + +## 打印变量 + +- 局部变量 + + ```shell + dv + ``` + +## 流程控制 + +g 继续 + + + + + +## 函数 + +u + address 查看函数汇编 + + + +## 模块 + +lmvm模块名 查看模块详细信息 + +![image-20200929174545996](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200929174545996.png) + +例: + +![image-20200929182901320](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200929182901320.png) + + + + + +x 列出模块的符号 + +![image-20201009110236170](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20201009110236170.png) +``` + +`Windbg/Windows XP调试环境.md`: + +```md +由于在xp下,virtualkd不能使用,所以不借助virtualkd + +## 虚拟机配置 + +![vm](https://raw.githubusercontent.com/supermanc88/ImageSources/master/1559628616(1).jpg) + + +## 在xp下运行msconfig + +![xp](https://raw.githubusercontent.com/supermanc88/ImageSources/master/1559628917(1).jpg) +打开调试 + +## windbg配置: + +windbg启动参数添加 +```shell +-b -k com:port=//./pipe/com_1,baud=115200,pipe +``` + + +``` + +`Windbg/devobj--drvobj.md`: + +```md +## !devobj + +!devobj 命令显示一个DEVICE_OBJECT结构体信息 + +``` +!devobj DeviceObject +``` + +> DeviceObject 可以是16进制的地址,也可以是设备的名称 + + +## !drvobj + +!drvobj 命令同上,显示一个DRIVER_OBJECT结构体信息 + +``` +!drvobj DriverObject +``` + +``` + +`Windbg/patchguard.md`: + +```md +# PatchGuard + +Kernel Patch Protection (KPP),内核修补程序保护(KPP),非正式的称为PatchGuard,俗称PG + +## 怎样触发 +- 修改系统服务表 +- 修改中断描述符表(IDT) +- 修改全局描述符表(GDT) +- 使用内核未分配的内核堆栈 +- 修改或修补内核本身,或HAL或NDIS内核库 + +``` + +`Windbg/scripts/MatchString.txt`: + +```txt +bu `filelock!efilelock.cpp:2659` +r $t1=poi(pFilePolicy); +ad *; +as /mu ${/v:buf} @$t1; +r $t2=$spat("$buf","*WRD*") +.if(@$t2==0){gc;} + + + +``` + +`Windbg/windbg命令关键字.md`: + +```md +# 命令关键字(待续) + +## ; 命令分隔符 +命令从左往右顺序执行。少部分命令后不能跟分号,因为它们会把行中剩下的全部内容都当成参数。包括`as(Set Alias)`、 +``` +$<(Run Script File) +``` +``` +$><(Run script file) +``` +以及任何以`*(comment line specifier)`关键字打头的命令。 + +## {} 块分隔符 +一对大括号常用来括住调试器命令程序中的一段语句 +### 语法 +``` +Statements {Statements} Statements +``` +### 注释 +进入每个语句块时,会计算该语句块中的所有别名值。 +每个块都必须在流程控制记号的后面。如果只是为了计算别名想建立一个块,应该带上.block关键字前缀。 + +## ${} 别名解释器 + +## $$ 注释说明符 +如果命令开头出现$$,则该行剩下的部分被当成注释,除非碰到分号。 + +## * 注释行说明符 +如果命令开头带星号,则行中剩下的部分被当成注释,即使中间有分号。 + +``` + +`Windbg/伪寄存器.md`: + +```md +# 伪寄存器 + +伪寄存器分为:自动伪寄存器、自定义伪寄存器 + +所有的伪寄存器都以一个`$`打头,如果使用MASM语法,可以在前面添加一个`@`,如果使用的是C++语法,则`@`是必须的。 + + +## 自动伪寄存器 +以下仅列出常用的几个伪寄存器 + +|伪寄存器|描述| +|-----|-----| +|$ea|最后一条被执行指令的有效地址| +|$ra|当前堆栈的返回地址。例如,g @$ra 将一直执行到返回地址| +|$ip|指令指针寄存器,同eip、rip| +|$proc|当前进程的地址,也就是EPROCESS块的地址| +|$thread|当前线程的地址,也就是ETHREAD块的地址| +|$peb|当前进程的进程环境块PEB的地址| +|$teb|当前线程的线程环境块TEB的地址| +|$tpid|PID| +|$tid|当前线程的线程ID| + +## 自定义伪寄存器 + +有20个自定义伪寄存器:$t0,$t1,...,$t19。它们是可以通过调试器读写的变量。能用来保存任意整数值。做为循环的变量时非常有用。 + +``` + +`Windbg/使用ip调试.md`: + +```md +# 使用IP方式进行Windbg调试 + +首先要确认两台计算机可以互相ping通 + +假设主机ip:192.168.137.1 + +被调试机ip:192.168.137.12 + +## 主机 +运行cmd +``` +windbg -k net:port=50000,key=1.2.3.4 +``` +> 此时windbg会弹出防火墙提示,全部允许 +## 被调试机 + +以管理员身份打开cmd +``` +# 启动调试 +bcdedit /set {default} debug yes +# 启用测试签名 +bcdedit /set testsigning on +# 设置主机的ip地址 +bcdedit /dbgsettings net hostip:192.168.137.1 port:50000 key:1.2.3.4 +# 确认dbgsettings配置正确 +bcdedit /dbgsettings +``` + +> 在配置dbgsettings的时候可以不用设置key,系统会自动生成,但要保证两台机器的key的一致 + + +``` + +`Windbg/内存操作.md`: + +```md +## 写内存到文件 + +The `.writemem` command writes a section of memory to a file. + +```Shell +.writemem FileName Range +``` +``` + +`Windbg/堆栈.md`: + +```md +## 查看堆栈 + +![image](./images/1545125826(1).jpg) + +### child-sp 和 retAddr有什么规律 + +![image](./images/1545126200(1).png) + +![image](./images/1545126337(1).jpg) + + +``` + +`Windbg/搜索内存.md`: + +```md +# 内存搜索 s(Search Memory) + +s命令搜索内存查找指定模板 + +## 语法 + +``` +s [-[[Flags]]Type] Range Pattern +s -[[Flags]]v Range Object +s -[[Flags]]sa Range +s -[[Flags]]su Range +``` + +## 参数 + +### [Flag] 搜索选项 + +指定一个或多个搜索选项,每个标志都是一个字母,必须将标志放在中括号中。中括号中不能有空格,标志n或l和它们的参数不放在中括号中。 + +例: +``` +s -[sw]Type Range Pattern +``` +|标志|含义| +|---|---| +|s|保存当前搜索的所有结果,之后可以使用该结果来进行重复的搜索| +|r|将当前搜索限制在上一次搜索的结果中,一个命令不能同时使用s和r,使用r时,Range将被忽略,并且调试器仅搜索上一次s命令保相下来的结果| +|n (Hits)|指定使用s标志时要保存的结果个数,默认是1024个结果。如果n和其他标志一起使用,n必须是最后一个标志,后面跟它的Hits参数。如果使用s标志的搜索发现了比指定值更多的结果,则会显示overflow error信息来提示所有的结果都不会被保存。| +|l (Length)|使得对任意ASCII或Unicode字符串的搜索只返回比length个字符长的结果。默认值为3。这个值只在使用-sa或-su标志时有用| +|w|只搜索可写入的内存区域。| +|1|在搜索输出中仅显示匹配项的地址| + + +### Type + +指定要搜索的内存类型。在type前加上-。 + +|类型|说明| +|---|---| +|b|byte| +|w|word| +|d|dword| +|q|qword| +|a|ascii string| +|u|unicode string| + +如果省略掉type,则会使用字节值。但是如果使用flags,就不能省略type + +### sa +搜索任何包含可打印的ascii字符串的内存。 + +### su +搜索任何包含可打印的unicode字符串的内存。 + + + +``` + +`Windbg/支持Windows7 x64的kdusb3.md`: + +```md +USB3调试线就是普通的连接线,十几块钱,剥开剪断里面的红色、白色、绿色的线,或者扣掉接头的1、2、3号金属片。 + +1.用usbview找到支持调试的xHCI对应的Bus, Device, and Function numbers,然后用十六进制编辑工具修改kdusb.dll文件偏移0x2850处的三个字节。我的测试电脑上xHCI的b.d.f是0.20.0,所以这里三个字节是0、0x14、0。 + +2.**替换系统自带的kdusb.dll文件** + +3.修改被调试系统的BCD文件,开启内核调试 +``` +bcdedit /dbgsettings usb targetname:usbdbg + +bcdedit /set debug on +``` + +4.主机上运行windbg +``` +windbg.exe -d -k usb:targetname=usbdbg +``` + +5.启动被调试电脑,按F8选择“禁用驱动程序强制签名” + +![image](./images/5712_4TBMWN5T83GFATE.png) + + +[原文链接](https://bbs.pediy.com/thread-248836.htm) + +``` + +`Windbg/数值表达式语法.md`: + +```md +# 数值表达式语法 + +调试器可接受两种不同的数值表达式:C++表达式和MASM表达式 + +## MASM表达式 +> 在MASM表达式中,任意符号的数值都是它的**内存地址**。可以在符号名前面加上一个`!`前缀来指明地址关联到的模块。 + +符号名可以用模块名来进行限制。使用一个感叹号(!)来分隔模块名和符号(例如,mymodule!main)。没有使用模块名的时候,符号也可以用感叹号作为前缀。即使对于局部变量,使用不带模块名的感叹号也尤其有用,它可以向调试器命令指明某个参数是一个名字而不是16进制数字。例如,如果没有加上感叹号前缀或者-n选项的话,变量fade会被命令dt (Display Type)当作一个地址来读取。但是,可以通过加上美元符号( $ )和感叹号( ! )当作前缀来指明一个符号是局部变量,如$!lime。 +> 两个冒号(::)或者两条下划线(__)可以用来标志类的成员。 +> +> 只有以模块名和感叹号为前缀的符号名中才能使用重音符号(`)或者撇号(')。 + +### MASM表达式中的寄存器和伪寄存器 +所有寄存器和伪寄存器右心包含单个at符号(@)前缀,使用该前缀可以使调试器存取这些值更快,该记号对于大多数x86通用寄存器不需要,对于其它寄存器和伪寄存器实际上也不是必须的,但强烈建议使用。 + +## C++表达式 + + +## 符号语法的区别 + +- **在MASM表达式中,每个符号都被解释成地址** +- 在C++表达式中,符号根据它的类型来解释。根据引用内容的不同,可能被解释为整数、数据结构、函数指针或任何其他数据类型 +**如果使用MSAM表达式语法,任何可以被解释为16进制数或寄存器的符号(例如,BadFeed, ebX)都必须用感叹号作为前缀。这能确保调试器将它识别为符号。** + + +``` + +`Windbg/有条件的执行命令--条件断点.md`: + +```md +## j (执行if else) + +``` +j Expression Command1 ; Command2 +j Expression 'Command1' ; 'Command2' +``` + + +通过表达式求值来判断执行cmd1还是cmd2,如果表达式求值为TRUE,则执行cmd1,否则执行cmd2. + +可以使用单引号包围命令字符串并使用分号分隔命令来组合多个命令。如果命令字符串是单个命令,则单引号是可选的。 + +example: +``` +j (MySymbol=0) 'r eax';'r ebx; r ecx' +``` + + +## 条件断点 + +可以使用上述的j命令或`.if`命令组合`gc`命令打造条件断点 + +``` +bp Address "j (Conditon) 'Cmd1'; 'gc' " +或 +bp Address ".if (Condition) {cmd1} .else {gc}" +``` + + +具体参考:https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/setting-a-conditional-breakpoint + + + +``` + +`Windbg/运行script file.md`: + +```md +## $<, $><, $$<, $$><, $$>a< (Run Script File) + +The `$<`, `$><`, `$$<`, `$$><`, and `$$>a<` commands read the contents of the specified script file and use its contents as debugger command input. + +```script +$a u win32k!NtUserSendInput +win32k!NtUserSendInput: +92c915cd ?? ??? + ^ Memory access error in 'u win32k!NtUserSendInput' +``` + + +OSR解答: + +> When looking at session space you need to switch to a process from +the appropriate session. If you just want to disassemble win32k code, +any interactive process will do (e.g. explorer.exe): + + +win32k.sys其实实现了shadow ssdt的功能函数,在非gui线程中,内存是不可读的。 + + +``` + +`Windows Driver Development/1.0-IRP堆栈探究.md`: + +```md +发送到驱动设备的请求都在IRP中。每个设备都有设备堆栈。 + +要向设备发送读取、写入或控制请求,I/O管理器将找到设备的设备节点,然后将IRP发送到该节点的设备堆栈。无论涉及多少设备堆栈,参与I/O请求的驱动程序的整体顺序称为请求的驱动程序堆栈。 + + +## I/O请求包 +IRP I/O request packet + +当一个线程调用一个I/O API的时候,I/O管理器构造一个IRP,用于在I/O系统处理此请求的过程中代表此请求。 + + +### IRP 快查表 + +针对每个处理器都有三个快查表: +- 小IRP快查表:保存的是只有一个栈单元(stack location)的IRP +- 中IRP快查表:保存的是有4个栈单元的IRP +- 大IRP快查表:保存的是有超过4个栈单元的IRP,在默认情况下,大IRP快查表保存具有10个栈单元的IRP + +> 系统会每分钟一次根据最近被请求过的栈单元数量来调整所分配的栈单元的数量,最大为20个 + +## IRP栈单元 +``` +// +// I/O Request Packet (IRP) definition +// + +typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) _IRP { + CSHORT Type; + USHORT Size; + + // + // Define the common fields used to control the IRP. + // + + // + // Define a pointer to the Memory Descriptor List (MDL) for this I/O + // request. This field is only used if the I/O is "direct I/O". + // + + PMDL MdlAddress; + + // + // Flags word - used to remember various flags. + // + + ULONG Flags; + + // + // The following union is used for one of three purposes: + // + // 1. This IRP is an associated IRP. The field is a pointer to a master + // IRP. + // + // 2. This is the master IRP. The field is the count of the number of + // IRPs which must complete (associated IRPs) before the master can + // complete. + // + // 3. This operation is being buffered and the field is the address of + // the system space buffer. + // + + union { + struct _IRP *MasterIrp; + __volatile LONG IrpCount; + PVOID SystemBuffer; + } AssociatedIrp; + + // + // Thread list entry - allows queueing the IRP to the thread pending I/O + // request packet list. + // + + LIST_ENTRY ThreadListEntry; + + // + // I/O status - final status of operation. + // + + IO_STATUS_BLOCK IoStatus; + + // + // Requestor mode - mode of the original requestor of this operation. + // + + KPROCESSOR_MODE RequestorMode; + + // + // Pending returned - TRUE if pending was initially returned as the + // status for this packet. + // + + BOOLEAN PendingReturned; + + // + // Stack state information. + // + + CHAR StackCount; + CHAR CurrentLocation; + + // + // Cancel - packet has been canceled. + // + + BOOLEAN Cancel; + + // + // Cancel Irql - Irql at which the cancel spinlock was acquired. + // + + KIRQL CancelIrql; + + // + // ApcEnvironment - Used to save the APC environment at the time that the + // packet was initialized. + // + + CCHAR ApcEnvironment; + + // + // Allocation control flags. + // + + UCHAR AllocationFlags; + + // + // User parameters. + // + + PIO_STATUS_BLOCK UserIosb; + PKEVENT UserEvent; + union { + struct { + union { + PIO_APC_ROUTINE UserApcRoutine; + PVOID IssuingProcess; + }; + PVOID UserApcContext; + } AsynchronousParameters; + LARGE_INTEGER AllocationSize; + } Overlay; + + // + // CancelRoutine - Used to contain the address of a cancel routine supplied + // by a device driver when the IRP is in a cancelable state. + // + + __volatile PDRIVER_CANCEL CancelRoutine; + + // + // Note that the UserBuffer parameter is outside of the stack so that I/O + // completion can copy data back into the user's address space without + // having to know exactly which service was being invoked. The length + // of the copy is stored in the second half of the I/O status block. If + // the UserBuffer field is NULL, then no copy is performed. + // + + PVOID UserBuffer; + + // + // Kernel structures + // + // The following section contains kernel structures which the IRP needs + // in order to place various work information in kernel controller system + // queues. Because the size and alignment cannot be controlled, they are + // placed here at the end so they just hang off and do not affect the + // alignment of other fields in the IRP. + // + + union { + + struct { + + union { + + // + // DeviceQueueEntry - The device queue entry field is used to + // queue the IRP to the device driver device queue. + // + + KDEVICE_QUEUE_ENTRY DeviceQueueEntry; + + struct { + + // + // The following are available to the driver to use in + // whatever manner is desired, while the driver owns the + // packet. + // + + PVOID DriverContext[4]; + + } ; + + } ; + + // + // Thread - pointer to caller's Thread Control Block. + // + + PETHREAD Thread; + + // + // Auxiliary buffer - pointer to any auxiliary buffer that is + // required to pass information to a driver that is not contained + // in a normal buffer. + // + + PCHAR AuxiliaryBuffer; + + // + // The following unnamed structure must be exactly identical + // to the unnamed structure used in the minipacket header used + // for completion queue entries. + // + + struct { + + // + // List entry - used to queue the packet to completion queue, among + // others. + // + + LIST_ENTRY ListEntry; + + union { + + // + // Current stack location - contains a pointer to the current + // IO_STACK_LOCATION structure in the IRP stack. This field + // should never be directly accessed by drivers. They should + // use the standard functions. + // + + struct _IO_STACK_LOCATION *CurrentStackLocation; + + // + // Minipacket type. + // + + ULONG PacketType; + }; + }; + + // + // Original file object - pointer to the original file object + // that was used to open the file. This field is owned by the + // I/O system and should not be used by any other drivers. + // + + PFILE_OBJECT OriginalFileObject; + + } Overlay; + + // + // APC - This APC control block is used for the special kernel APC as + // well as for the caller's APC, if one was specified in the original + // argument list. If so, then the APC is reused for the normal APC for + // whatever mode the caller was in and the "special" routine that is + // invoked before the APC gets control simply deallocates the IRP. + // + + KAPC Apc; + + // + // CompletionKey - This is the key that is used to distinguish + // individual I/O operations initiated on a single file handle. + // + + PVOID CompletionKey; + + } Tail; + +} IRP; +``` + +IRP是由两部分组成的:一个固定的头以及一个或多个栈单元。 + +``` +(Irp)->Tail.Overlay.CurrentStackLocation +``` +这是一些区域,每个将要被调用的驱动程序占用一个区域,它们包含了每个驱动程序用于执行它这一部分工作所需要的信息。比如功能代码、参数和驱动程序环境信息等。 +当IRP被从一个驱动程序传递到下一个驱动程序时,又有额外的栈单元被填充上数据。 +可以把IRP想象成一个栈样来工作,在它的生命期中数据被加进去,又被移除出来。 + +> 由于一个指定栈上的设备数量是可以预先知道的,因此,I/O管理器将为该栈上的每个设备分配一个栈单元。 + + + +### 异步IRP + +异步操作,各层设备的主功能函数不等操作完成就返回了,可是各层设备驱动都可能有自己的善后函数,这些善后函数要等IRP所要求的操作完成时才能执行,所以就通过宏操作`IoSetCompletionRoutine`设置好相应`IO_STACK_LOCATION`数据结构中的函数指针`CompletionRoutine`,把善后函数的执行托付给DPC函数。所以,IRP的善后就包括自下而上地扫描IRP中的各层`IO_STACK_LOCATION`数据结构,如果字段`Control`中相应标志位所表示的条件得到满足就加以调用。 + + + + +``` + +`Windows Driver Development/10.0-进程与线程.md`: + +```md +# 进程与线程 + +> 在Linux内核中,进程或者是一个特殊的线程,即独立拥有地址空间的线程,或者是一组普通的共享一个地址空间的线程。**所以,每当创建一个进程,同时就自然创建了它的第一个线程,二者是合一的。实际上,创建进程就是通过创建线程来实现的。父进程通过系统调用`fork`一分为二,变成两个共享同一地址空间的线程,然后其中之一通过系统调用`execve`分道扬镳另立门户,这才变成了两个进程。** + +- 进程与线程是一对多的关系。 + +- 调试的单位是线程而不是进程。 + +- Linux 和 Windows创建进程/线程的系统调用在语义上有很大的区别。在Linux中,首先是父进程的“细胞分裂”,即分裂成两个线程,然后如果子进程另立门户就又变成两个进程。就是说,**创建线程是创建进程的必经之路。**而在Windows中,**创建进程和创建线程是两码事,线程只能创建在已存在的进程内部,所以必须是先创建进程,然后再创建线程。** + + +``` +NTSTATUS +NTAPI +NtCreateProcess(OUT PHANDLE ProcessHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, + IN HANDLE ParentProcess, + IN BOOLEAN InheritObjectTable, + IN HANDLE SectionHandle OPTIONAL, + IN HANDLE DebugPort OPTIONAL, + IN HANDLE ExceptionPort OPTIONAL) + + +NTSTATUS +NTAPI +NtCreateThread(OUT PHANDLE ThreadHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, + IN HANDLE ProcessHandle, + OUT PCLIENT_ID ClientId, + IN PCONTEXT ThreadContext, + IN PINITIAL_TEB InitialTeb, + IN BOOLEAN CreateSuspended) + +``` + +系统调用`NtCreateProcess`只创建进程,而不包括其第一个线程,所以`CreateProcess`在调用`NtCreateProcess`以后还要立即调用另一个系统调用`NtCreateThread`,以创建其第一个线程。 + + +进程需要个“进程控制块”,线程需要有“线程控制块”。 + +Windows内核中与进程有关的数据结构有: + +- EPROCESS。 即“(执行体)进程控制块”,它代表着Windows的一个进程,“E”表示“执行体(Executive)”,微软把Windows内核中的上层称为“Executive”。 + +- KPROCESS。 这是`EPROCESS`内部的一个成份,其名称叫`PCB`,是“核心层进程控制块”。 + +- W32PROCESS。 在用户空间有个“windows 子系统”的服务进程`csrss`。之前这个服务进程为系统中的每个“窗口进程”都维持着一个数据结构,其中包含了一些与窗口和图形界面相关的信息。对于窗口和图形界面的操作也是由csrss请求实现的。但是为了提高效率,后来把这部分功能移到了内核中。与此相应,有关的数据结构的主体也移到了内核中。 + +内核中与进程有关的数据结构实际上就只有两种,就是`EPROCESS`和`W32PROCESS`,前者是每个进程都有,后者是只有“视窗进程”才有。 + + +用户空间中与进程有关的数据结构是“进程环境块”PEB(Process Environment Block)。PEB中记录着进程的运行参数、映像装入地址等信息。PEB在用户空间中的位置是固定的,总是在`0x7ffdf0000`。 + + +EPROCESS这个结构体很大,其中第一个成员就是Pcb + + + +线程有`ETHREAD`,`KTHREAD` + + +`KTHREAD`叫tcb + +`KTHREAD`中有teb + + + + +0xffdf0000 地址是`KUSER_SHARED_DATA`数据结构的起点 + + +--- + +## 获取当前进程或线程 + +在 Intel x86 处理器的Windows系统中,**FS寄存器指向一块被称为处理器控制区(PCR,Processor Control Region)的内存,其数据类型为KPCR。KPCR有一个类型为KPRCB的数据成员PrcbData,这是一个当前处理器的控制块(Processor Control Block),其中包含了指向当前线程的KTHREAD结构的指针。** + +这里之所以从 ApcState 成员中获得进程结构指针,而不是从 KTHREAD 的 Process 域或 ETHREAD 的 ThreadsProcess域获取进程结构指针,是因为即使当前线程附载到其他的进程中(通过KeAttachProcess),或者又回到原来的进程中(通过KeDetachProcess),这种做法总是能够获取正确的当前进程结构指针。 +``` + +`Windows Driver Development/2.0缓存管理器.md`: + +```md +# 缓存管理器 + +缓存管理器是一组内核模式的函数和系统线程,它们与内存管理器协同工作,为所有的**Windows文件系统驱动程序**提供了数据缓存的能力。 + +## 缓存管理器的特性: +- 支持所有的文件系统类型 +- 使用内存管理器来控制哪些文件的哪些部分位于物理内存中 +- 以虚拟块(即在一个文件内部的偏移)为基础来缓存数据 +- 允许应用程序在打开文件时传递“提示” +- 支持可恢复的文件系统 + +### 单个中心化的系统缓存 +有些操作系统信赖于每一个单独的文件系统来缓存数据,而Windows提供了一个中心化的缓存设施,它可以缓存所有外部保存的数据。 + +> 缓存的用途是将频繁被访问的数据的一个子集高妙在物理内存中,作为提高I/O性能的一种方法。 + +### 内存管理器 +缓存管理器的一个非同寻常的方面是,它从来不知道有多少被缓存的数据真的在物理内存中。 +这是因为:**它访问的数据的做法是将文件的视图映射到系统虚拟地址空间中,它使用的标准的内存区对象。随着这些映射视图中的地址陆续被访问,内存管理器把那些不在物理内存中的数据块换到物理内存中。当内存需求吃紧时,内存管理器又把缓存中的数据换出去,如果它们被修改过的话,还把它们写回到那些文件中。** + +通过使用映射文件并且在虚拟地址空间的基础上进行缓存,缓存管理器避免产生读或写I/O请求包来访问当前正在缓存的文件的数据。 + +### 缓存的一致性 +缓存管理器的一个重要的功能是,确保任何进程在访问被缓存的数据时,问题得到该数据的最新版本。 + +内存管理器保证,每个被映射的文件,不管有多少内存区对象或映射的视图,它都只有一份内部表示。 + +### 虚拟块缓存 +虚拟块缓存,它跟踪、记录了哪些文件的哪些部分位于缓存中。 + +### 流式缓存机制 +缓存管理器也被设计成可进行流式缓存,以区别文件缓存。流是指一个文件内部的字节流。实际上,与其说缓存管理器缓存的是文件,不如说它缓存的是流(所有的文件至少有一个数据流),这些流通过一个文件名和一个流名(如果该文件中存在多个流的话)来标识。 + +### 对可恢复文件系统的支持 +一个可恢复的文件系统需要维护一个日志文件,它每次往卷上做一个涉及文件系统结构(文件系统的无数据)的修改以前,都要在日志文件中记录下此更新操作。如果系统失败了,正在进行之中的卷修改操作被打断了,那么,这一可恢复文件系统利用日志中保存的信息,重新发出这些卷更新操作。 + +## 缓存的虚拟内存管理 +缓存管理器是按照虚拟地址为基础来缓存数据的,通过一种叫作**虚拟地址控制块(VACB,virtual address control block)**的结构来管理它们。**VACB将这些地址空间区域定义为256KB的槽,称为视图。** + +当缓存管理器在引导过程中做初始化时,它分配出一个初始的VACB数组来描述被缓存的内存。当缓存需求增长,需要更多内存的时候,缓存管理器则根据需要分配更多的VACB数组。当其他内存需求对系统产生压力时,它也会收缩它的虚拟地址空间。 + + +在一个文件的第一个I/O(读或写)操作时,缓存管理器把该文件中包含所请求数据的256KB对齐的区域,映射到级纺缓存地址空间中的一个空闲槽中。例如,如果从300000字节偏移开始,有10个字节被读入到一个文件中,那么,被映射的视图将从偏移262144处开始(即该文件第二个256KB对齐的区域),视图的守度为256KB。 + +如果缓存管理器需要映射一个文件的视图,而系统缓存中又没有足够的空闲槽,姥,它将解除最早被映射的不活动视图,并使用该槽,如果没有视图可使用,则返回一个I/O错误,表明没有足够久系统资源可以用来完成该操作。 + +## 缓存的大小 + +### 缓存的虚拟大小 + +在32们windows系统上,系统缓存的虚拟大小公受限于内核模式虚拟地址空间的数量和可以选配的SystemCacheLimit注册表键的值。在64位windows上,虚拟缓存最大的大小是1024GB + +### 缓存的工作集大小 +系统缓存并没有它自己的工作集,而是与系统的其他部分共用同一个工作集,此工作集包含缓存数据、换页内存池、可换页的Ntoskrnl代码,以及可换页的驱动程序代码。 + +### 缓存的物理大小 +被缓存的文件数据的总数量包括系统工作集、修改页面列表和备用列表。这些部分的大小都是由内存管理器来控制的,因此,从某种意义上说,内存管理器都是真正的缓存管理器。 + +当缓存管理器解除一个视图的映射时,内存管理器并没有丢掉在系统缓存的工作集中对应于该视图的文件数据,它只是把这些数据移到备用列表中。如果不存在任何其他的物理内存需求,那么,备用列表几乎可以消耗掉系统工作集之外的所有物理内存。 + + +## 缓存的数据结构 + +### 系统范围的缓存数据结构 +缓存管理器利用一个数据结构数据来跟踪系统缓存中视图的状态。此数组中的数据结构称为虚拟地址控制块数组,这些VACB数组被保存在非换页池中。 + +在一个32位系统上,每个VACB的大小是32字节,而每个VACB数组的大小是128KB,因而每个数组有2096个VACB。在64位系统上,每个VACB的大小是64字节,因而每个数组有2048个VACB。 + +在系统初始化过程中,缓存管理器分配一个初始的VACB数组,并把它链接到一个系统范围的VACB数组的列表,名为CcVacbArrays。 + +每个VACB数组都由两种VACB组成:低优先级映射VACB和高优先级映射VACB。系统为每个VACB数组各分配出64个初始的高优先级VACB。高优先级VACB的不同之处在于,它们的视图是从系统地址空间中预先分配出来的。当映射某些数据时,内存管理器可能已经没有更多的视图提供给缓存管理器了,此时,如果映射请求被标记为高优先级,那么缓存管理器将会使用一个预先分配的、位于高优先级VACB中的视图。通常,映射的优先级被设置为默认值“低”。 + +p348 +VACB中的第一个域是数据在系统缓存中的虚拟地址。第二个域是一个指针,指向共享的缓存表结构,它标识了哪个文件被缓存。第三个域标识了该视图在文件内部的偏移。当缓存管理器释放一个VACB时,第四个域被用来把它链接到一个最近最少使用(LRU)VACB列表;缓存管理器在分配新VACB时会首先检查这个列表。最后,第五个域把该VACB链接到VACB数组的头部,以便表示它被保存在哪个VACB数组中。 + +### 针对每个文件的缓存数据结构 +每一个打开的文件句柄都有一个对应的文件对象,如果该文件被缓存了,那么,该文件对象指向一个私有的缓存表结构,此表结构包含了最近两次读的位置,所以,缓存管理器可以执行智能预读。而且,针对同一个文件的已打开实例的所有私有缓存表被链接在一起。 + +每一个被缓存的文件(而不是文件对象)都有一个共享的缓存表结构,它描述了被缓存的文件的状态,包括它的大小和有效数据长度。 + +共享缓存表也指向了由内存管理器所维护的内存区对象(它描述了方尺文件被映射到虚拟内存的情况)、与该文件相关联的私有缓存表的列表,以及任何描述了该文件当前在系统缓存中的映射视图的VACB。 + +p352 + +## 文件系统的接口 +当一个文件的数据第一次被读操作或写操作访问时,文件系统驱动程序负责确定该文件是否已经有一部分被映射到系统缓存中了。如果还没有,那么,文件系统驱动程序必须调用`CcInitializeCacheMap`函数,以建立缓存。 + +一旦一个文件已经被建立起缓存方式的访问环境,文件系统驱动程序就调用几个函数中的某一个,来访问该文件中的数据: +- 拷贝方法,在系统空间的缓存缓冲区和用户空间的进程缓冲区之间拷贝用户数据。 +- 映射和锁定的方法,使用虚拟地址来直接读写数据到缓存缓冲区中。 +- 物理内存访问方法,使用物理地址来直接读写数据到缓存缓冲区中。 + +> 文件系统驱动程序必须提供两个文件读操作版本(缓存和非缓存的),以避免当内存管理器处理页面错误时导致无限循环。内存管理器通过调用文件系统来从文件中获取数据,以处理一个页面错误时,它必须在IRP中设置“非缓存”标志,以指定非缓存的读操作。 + + +### 从缓存中来回拷贝数据 +因为系统缓存位于系统空间中,因此,它被映射到每个进程的地址空间中。用户应用程序若要读写被缓存的文件,则必须通过内核模式的全程来获得服务;这些内核模式全程在“系统空间的缓存缓冲区”和驻留在进程地址空间中的应用程序缓冲区”之间拷贝数据。 + +### 通过映射和锁定接口进行缓存 +如果一个文件系统驱动程序需要读缓存中的文件系统无数据,那么,它调用缓存管理器的映射接口来获得目标数据的虚拟地址。缓存管理器寻找所有被请求的页面,并把它们融入到内存中,然后将控制返回组文件系统驱动程序。于是,文件系统驱动程序可以直接访问这些数据。 + +### 通过直接内存访问接口进行缓存 +缓存管理器还提供了第三个访问缓存中的数据的接口:直接内存访问(direct memory access DMA)。通过DMA函数,可以直接读或写系统缓存的页面,而无需经由缓冲区的介入。 + +DMA接口鬼魂被缓存的用户数据的物理地址(而不是虚拟地址,映射和锁定接口返回的是虚拟地址)返回级文件系统。这些物理地址可以直接被用于在千载一时内存和网络设备之间传输数据。DMA可以为网络服务器在处理远程系统的文件请求时,带来显著的性能改进。为了描述这些对于物理内存的引用,需要用到内存描述符列表(memory descriptor list, MDL)。 + + +## 快速I/O +无论何时,只要有可能,对于被缓存的文件的读和写操作都是由一种称为快速I/O(Fast I/O)的调整机制来处理的。快速I/O是一种对于被缓存文件的读写方法,它无需经过"生成一个IRP"这样的环节。通过快速I/O,I/O管理器调用文件系统驱动程序的快速I/O例程,以确定该I/O是否可以直接由缓存管理器来满足,而无需生成一个IRP。 + +``` + +`Windows Driver Development/3.0MiniFilter的Work Item.md`: + +```md +Minifilter提供支持通用的工作项目队列(queuing of "generic" work items) + +工作项是和Minifilter驱动或Minifilter驱动实例相关的,而不是I/O操作 + +MiniFilter通过调用`FltQueueGenericWorkItem`将工作项插入系统工作队列。 + +``` + +`Windows Driver Development/4.0内存管理.md`: + +```md +## 内存管理 + +32位的虚拟内存空间按照0--4G的线性地址看待。 + +任何一个进程都定义了它自己的完整的4G空间,但是,其2G-4G之间的部分是所有进程共享的,称为**系统地址空间**,而0-2G的部分才是它自己私有的,称为**进程地址空间**。 + +为了有效的管理2G系统地址空间,Windows在初始化时将这2G划分成了一些固定的区域,各个区域有专门的用途。而且,**Windows内核使用了一组全局变量来记录每个区域的边界**。所以,系统地址的初始化实际上是对这些全局变量的初始化,并相应的初始化每个区域。Windows的一些引导选项以及系统配置(位于注册表)可能会影响到某些区域的位置和大小。 + +> 系统空间中的主要区域包括:内核模块映像、PFN数据库、换页内存池、非换页内存池、会话空间、系统缓存区、系统PTE区域、系统视图以及页表等。 + +## 内存管理器 + +内存管理器的一个重要任务是管理有限的物理内存。在windows的系统地址空间中,专门保留了一个称为**PFN数据库(page frame number database,页帧编号数据库)**的区域。每一个物理页面都对应于PFN数据库中的一项,此PFN项描述了该页面的状态。Windows支持八种状态:**活动、备用(standby)、已修改、已修改但不写出、转移、空闲、零化和坏状态**。 + + + +内存管理器有以下两个主要任务: +- 将一个进程的虚拟地址空间转译或者映射到物理内存中,这样,当一个在该进程的环境中运行的结各读写虚拟地址空间时,它可以引用到正确的物理地址[在一个进程的虚拟地址空间中,物理上驻留在内存中的那一部分子集被称为工作集]。 +- 当内存被过度提交(也就是说,当运行结各或者系统代码试图使用比当前可用的量不过尔尔的物理内存)时,将内存中的有些内容转移到磁盘上;并且,在以后需要这些内容时,再将它们读回到物理内存中。 + +除了提供虚拟内存管理以外,内存管理器还提供了一组核心服务; +- 内存映射文件[在内部称为**内存区对象**] +- 写时复制内存 +- 为应用程序使用大的、稀疏的地址空间提供支持 +- 内存管理器提供了一种方法,使进程能够分配和使用的物理内存可以超过总共可被映射到进程虚拟地址空间中的大小(32位操作系统可以使用3G以上内存) + +### 内存管理器组件 + 内存管理器是Windows执行体的一部分,所以它位于文件Ntoskrnl.exe中。由以下组件构成: + - 一组执行体系统服务,负责分配、释放和管理虚拟内存。绝大多数服务通过Windows API或者内核模式的设备驱动程序接口暴露出来。 + +--- + +虚拟内存机制 或 页式内存管理 + +这是由CPU芯片内部(或外部)的`存储管理单元`MMU支持的。 + +采用页式内存管理时,程序中所使用的内存地址。CPU中的运算单元`ALU`所发出的都是“虚拟地址”,虚拟地址是不能直接用来访问物理内存的,需要由`MMU`将其“映射”到某个物理页面上转换成“物理地址”才能访问。 + +![image](./images/1568883056(1).jpg) + +当MMU不存在时,或者没有被启用,那么ALU所使用的虚拟地址等同于物理内存所使用的物理地址。如果MMU存在并已被启用,由ALU所发出的虚拟地址就会由MMU映射成物理内存所用的物理地址。虚拟地址与物理地址在形式上并无区别,只是数值上的变化。**这种映射是以`页面`为单位的,而不是以单个地址为单位的,所以在实际映射的过程中需要进行一些计算和拼接。**MMU还担负着`检查访问权限`的任务,可以根据CPU的当前状态和所欲进行的访问形式而决定是否允许。 + + +- 每个进程的虚拟地址的范围是相同的,但是实际映射的却是物理内存中的不同的部位。 +- 不同进程的虚拟地址被映射到物理内存中不同的页面集合,每个进程都有自己的映射,互相之间没有交集,这就解决了`进程隔离` +- 把CPU的运行状态分成“用户态”和“内核态”,其虚拟地址范围也分成两部分,一部分用于内核,另一部分用于用户程序。 + + +> 当系统中有多个进程并发运行时,所需存储空间的总合大于实际内存,这样就有必要对物理内存加以复用,**就是使同一个物理内存页面在不同的时间可被用于不同进程虚拟内存页面,而暂时不会被用到的虚拟内存页面的内容,则可以被存储在磁盘上。** + + +## MMU是怎么知道哪个虚拟内存应当被映射到哪个物理页面? + +页面映射表:这实际上是个以虚拟内存页面号为下标的数组,数组中的元素称为页面映射表项,表项的内容决定了相应的虚存页面是否有映射、映射到哪一个页面,以及该物理页面的保护模式。 + +每当需要访问一个虚存页面时,MMU就以虚存页面号为下标从页面映射表中找到相应的表项,进而确定应该映射到哪一个物理页面,以及是否允许所要求的访问。 + +每个进程都有自己的页面映射表,CPU在执行哪一个进程的程序,就要哪个进程的页面映射表,为此x86处理器有个专门的控制寄存器`CR3`,这个寄存器总是指向当前进程的页面映射表(这里使用的是物理地址)。 + +如:以4KB的页面大小为例,4GB的虚存空间划分成1M个页面,所以整个页面映射表可以有1M个表项,每个表项4个字节,一共就是4MB的存储空间。 + + +### 虚存的每次访问量否都要两次访问物理内存? +1. 访问CR3 ,找到映射表 +2. 根据映射表找到所访问的物理地址 + +以上效率是很慢的。 + +解决办法: +CPU根据实际需要把当前用到的`页面映射表项`高速缓存在内部的`TLB`中。`TLB`意为`地址转换便查缓冲区(Translation Look-aside Buffer)`,是CPU内部一块专用的、特殊(只读)的高速缓存。每当需要使用一个页面映射表项时,MMU首先在TLB中寻找,找到就不需要访问物理内存了,找不到才从物理内存装入所需的表项。 + +后面这点有点难懂 +![image](./images/1568886968(1).jpg) + +``` + +`Windows Driver Development/5.0 FCB结构.md`: + +```md +## FCB (File Control Block) + +用文件对象来代表一个文件是不现实的。因为同一个文件可能被多次打开,每次打开都能生成一个文件对象。 + +更合理的是使用FCB来代表一个文件。FCB是文件系统为每个打开着的文件生成的一个内存区,保存维护这个文件所需要的数据。无论这个文件打开了多少次,都只有一个FCB。 + +FCB结构由文件对象中的`FsContext`字段指向。 + +**FCB 、文件对象 和 文件句柄的关系** + +- FCB对应一个已经被打开的实体的文件,用于文件系统维护文件。所有对某个文件的操作,本质上都会通过在FCB中记载的信息进行。 +- 文件对象对应一个文件的一次打开。在打开时产生,在关闭时销毁,因此多个文件对象可以对应同一个FCB。文件对象的指针用来作为IRP的域之一发送文件请求(本质上文件系统收到请求后会通过FCB中保存的信息进行操作)。 +- 文件句柄是从文件对象上获得的。一个文件对象可以被获得多个文件句柄,句柄一般用来在ZwWriteFile或者ZwReadFile之类的内核API中对文件进行操作。 + +共享FCB的所有操作都指向同一文件。FCB是文件操作的焦点。由于同一FCB上的操作实际上在同一文件上,因此同步基于FCB而不是某些更高级别的对象。 + +[OSR关于文件句柄和文件对象的解答](https://community.osr.com/discussion/122170/fileobjects-and-file-handles) + +1. 是不是每次打开文件都会创建一个FileObject? + + > 答:是。每一个CreateFile都会创建一个新的FileObject + +2. 当多个并发的线程打开一个文件时,是一个文件句柄对应一个FileObject还是多个文件句柄对应一个FileObject? + + > 答:1:1 + +3. 当文件句柄被复制时,会不会产生新的FileObject? + + > 答:不会。当句柄复制时,多个文件句柄会共享一个FileObject。 +``` + +`Windows Driver Development/5.1-CCB结构.md`: + +```md +## CCB (Context Control Block) + +CCB只是一个句柄或由FSD(File System Driver)创建和维护的上下文,用于呈现磁盘上对象的开放实例。例如,当用户应用程序对文件执行打开操作时,如果打开请求成功,它将从操作系统接收句柄。对应于这个句柄,一个Windows NT文件系统创建了一个CCB结构,它仅仅是用户句柄的内核等价. + + +``` + +`Windows Driver Development/6.0驱动读写方式.md`: + +```md +设备对象一共可以有三种读写方式,分别是缓冲区方式读写、直接方式读写、其他方式读写。 + +这三种方式的Flags分别对应为`DO_BUFFERD_IO`、`DO_DIRECT_IO`和0 + + +## 缓冲区读写 + +以缓冲区方式写设备时,操作系统将WriteFile提供的用户模式的缓冲区复制到内核模式地址下。这个地址由WriteFile创建的IRP的AssociatedIrp.SystemBuffer子域记录。 + +优点:比较简单地解决了将用户地址传入驱动的问题。 +缺点:需要在用户模式和内核模式之间复制数据,影响了运行效率。在少量内存操作时,可以采用这种办法。 + +## 直接方式读写 + +和缓冲区方式读写设备不同,直接方式读写设备,操作系统会将用户模式下的缓冲区锁住。然后操作系统将这段缓冲区在内核模式地址再映射一遍。这样,用户模式的缓冲区和内核模式的缓冲区指向的是同一区域的物理内存。无论操作系统如何切换进程,内核模式地址都保持不变。 + +操作系统先将用户模式的地址锁定后,操作系统用内存描述符表(MDL)记录这段内存。 + +[MDL](./6.1-MDL直接方式读写.md) + +``` + +`Windows Driver Development/6.1-MDL直接方式读写.md`: + +```md +## 直接方式读写操作 + +当CPU运行于系统空间的时候是可以直接访问用户空间的(反过来则不行)。 + +为用户空间缓冲区增添一个系统空间映射,这使同一组物理页面有了两个虚拟地址区间,其一就是原来的用户空间虚拟地址区间,**其二则是系统空间的虚拟地址区间。**于是,就可以通过系统空间的虚拟地址访问用户空间缓冲区了,直到完成操作而返回用户空间时才撤销系统空间的映射。**直接方式读写对于很小的缓冲区是不划算的,因为临时映射的建立和撤销需要一定的开销,对于大一点的缓冲区才合适。** + +操作系统会用户模式下的缓冲区所指向的物理内存在内核模式下再次映射一遍。这样,用户模式的缓冲区和内核模式的缓冲区指向的是同一区域的物理内存。 + +操作系统用**内存描述符表**(MDL数据结构)来描述虚拟内存缓冲区的**物理页面**布局。 + +![image]() + +**注意:用户模式的这段缓冲区在虚拟内存上是连续的,但是在物理内存上可能是离散的。** + + +MDL记录这段虚拟内存,这段虚拟内存的大小存储在mdl->ByteCount里,这段虚拟内存的第一个**页**地址是`mdl->StartVa`,这段虚拟内存的首地址对于第一个页地址的偏移量是`mdl->ByteOffset`。因此,这段虚拟内存的首地址是`mdl->StartVa+mdl->ByteOffset`。 + +MDL结构是半透明的,不要直接访问MDL的不透明成员。而是使用以下宏: + +- `MmGetMdlVirtualAddress`返回MDL描述的I/O缓冲区的虚拟内存地址。 +- `MmGetMdlByteCount`返回I/O缓冲区的大小 +- `MmGetMdlByteOffset`返回I/O缓冲区开头的物理页面内的偏移量。 +- `IoAllocateMdl`分配MDL +- `IoFreeMdl`释放MDL +- 可以分配一块非分页内存,危重通过调用`MmInittializeMdl`将此内存块格式化为MDL + +**The MmInitializeMdl macro initializes the header of an MDL.** + +`MmInitializeMdl`不会初始化MDL头之后的数据。对于驱动分配的非分页内存块,使用`MmBuildMdlForNonPagedPool`去初始化这个数据,以描述I/O缓冲区所在的物理内存。 + +- `MmProbeAndLockPages`检测指定的虚拟内存页,使它们长驻内存并锁定在内存 +- `MmUnlockPages`解锁MDL描述的物理页 +- `MmGetSystemAddressForMdlSafe`返回一个非分页的由MDL描述的系统空间的虚拟地址。 + +``` + +`Windows Driver Development/7.0-文件缓冲.md`: + +```md +# CcInitializeCacheMap例程 +文件系统调用`CcInitializeCacheMap`例程来缓存文件。 + +``` +VOID CcInitializeCacheMap( + _In_ PFILE_OBJECT FileObject, + _In_ PCC_FILE_SIZES FileSizes, + _In_ BOOLEAN PinAccess, + _In_ PCACHE_MANAGER_CALLBACKS Callbacks, + _In_ PVOID LazyWriteContext +); +``` + +## Callbacks +指向从非分页池分配的结构的指针,包含调用者提供的预读和后写回调例程的入口点。此结构及其成员定义如下: + +``` +typedef struct _CACHE_MANAGER_CALLBACKS { + PACQUIRE_FOR_LAZY_WRITE AcquireForLazyWrite; + PRELEASE_FROM_LAZY_WRITE ReleaseFromLazyWrite; + PACQUIRE_FOR_READ_AHEAD AcquireForReadAhead; + PRELEASE_FROM_READ_AHEAD ReleaseFromReadAhead; +} CACHE_MANAGER_CALLBACKS,* PCACHE_MANAGER_CALLBACKS; +``` + +除非文件是在禁用数据缓存的情况下创建的,否则文件系统必须在使用文件上的任何其他缓存管理器例程之前调用`CcInitializeCacheMap`来缓存文件。在大多数系统中,国碎情况下启用文件缓存,但可以通过在文件创建选项中将`FILE_NO_INTERMEDIATE_BUFFERING`标志设置为`TRUE`来禁用文件缓存。 + +调用`CcInitializeCacheMap`后,如果需要,文件系统可以调用`CcSetAdditionalCacheAttributes`来禁用预读或后写。 + +关闭文件时,每个支持文件缓存的文件系统都必须在该文件上调用`CcUninitializeCacheMap`,无论文件是否缓存。即使文件是在禁用缓存的情况下创建的,文件系统仍然必须调用`CcUninitialzeCacheMap`。 + +该`CcIsFileCached`宏确定文件是否被高速缓存。 + +``` +BOOLEAN CcIsFileCached( + [in] PFILE_OBJECT FileObject +); +``` + +``` + +`Windows Driver Development/8.0-中断请求级.md`: + +```md + +## 中断请求级(IRQL) + +中断请求划分为:软件中断和硬件中断,并将这些中断都映射成不同级别的**中断请求级(IRQL)**。 + +每个IRQ有各自的优先级别,正在运行的结各随时可以被中断打断,进入到中断处理程序。当优先级高的中断来临时,处在优先级低的中断处理程序,也会被打断,进入到更高级别的中断处理函数。 + +Windows将中断的概念进行了扩展,提出一个中断请求级的概念。其中规定了32个中断请求级别,分别是**0~2级别为软件中断,3~31级为硬件中断**,其中,数字从0到31,优先级别逐次递增。 + +![image](./images/1556422665(1).jpg) + +硬件的IRQL称为设备中断请求级,或者简称DIRQL。Windows大部分时间运行在软件中断级别中。当设备中断来临时,操作系统提升IRQL至DIRQL级别,并且运行中断处理函数。当中断处理函数结束后,操作系统把IRQL降到原来的级别。 + +> 用户模式的代码是运行在最低优先级的`PASSIVE_LEVEL`级别。驱动程序的DriverEntry函数、派遣函数、AddDevice等函数一般都运行在`PASSIVE_LEVLE`级别,它们在必要时可以申请进入`DISPATCH_LEVEL`级别。 + +> Windows负责线程调度的组件是运行在`DISPATCH_LEVEL`级别,当前的线程运行完时间片后,系统自动从`PASSIVE_LEVEL`级别提升到`DISPATCH_LEVEL`级别,当线程切换完毕后,操作系统又从`DISPATCH_LEVEL`级别降到`PASSIVE_LEVEL`级别。 + +驱动程序的`StartIo`函数和`DPC`函数运行在`DISPATCH_LEVEL`级别。 + +### 线程调试与线程优先级 + +所有应用程序都运行在`PASSIVE_LEVEL`级别上,它的优先级别最低,可以被其他IRQL级别的程序打断。线程优先级只针对应用程序而言,只有程序运行在`PASSIVE_LEVEL`级别才有意义。 + +> 这里的`PASSIVE_LEVEL`是级别最低的,但是却对应着系统结构中较高的层次。当CPU运行于用户空间,或者虽然进入了内核但还只是运行于管理层的时候,其运行级别就是`PASSIVE_LEVEL`。比其略高的是`APC_LEVEL`,那是在为APC函数的执行进行准备时的运行级别,APC请求相当于对用户空间程序的中断。 + +> 再高一级是`DISPATCH_LEVEL`,这大致相当于CPU运行于Windows内核中的核心层,即“内核”层。线程的切换只能发生于CPU行将从`DISPATCH_LEVEL`级别下降的时候。 + +线程优先级是指某线程是否有更多的机会运行在CPU上,线程优先级高的线程有更多的机会被内核调度。负责调度线程的内核组件运行在`DISPATCH_LEVEL`级别的IRQL上,这时候所有应用程序都停止,等待着被调度。 +![image](./images/1556433246(1).jpg) + +### IRQL与内存分页 + +在使用分页内存时,可能会导致页故障。因为分页内存随时可能从物理内存交换到磁盘文件。读取不在物理内存中的分页内存时,会引发一下页故障,从而执行这个异常的处理函数。异常处理函数会重新将磁盘文件的内容交换到物理内存中。 + +**对于等于或者高于`DISPATCH_LEVEL`级别的程序不能使用分页内存,必须使用非分页内存。驱动程序的StartIo例程、DPC例程、中断服务例程都运行在`DISPATCH_LEVEL`或者更高的IRQL。因此,在这些例程中不能使用分页内存,否则会导致系统崩溃。** + +`PAGED_CODE()`代码中使用此宏意思是这个函数所占的页面可以被倒换出来。 + +当然,光是在程序中引用宏操作`PAGED_CODE()`不会使一个函数所在的页面可倒换,真正使其可倒换的编译指示`#pragma alloc_text`。如`NtQueryObject`中的第一行就是`PAGED_CODE`,与此相应,这个函数所在的源文件中就有这么一行: + +``` +#pragma alloc_text(PAGE, NtQueryObject) + + +NtQueryObject() +{ + PAGED_CODE(); + ... + return; +} +``` + + +### 控制IRQL提升与降低 + +有些时候,驱动程序中需要提升IRQL级别。在运行一段时间后,再降回原来的IRQL级别。这样做的目的一般是基于同步处理的需要。 + +首先驱动程序需要知道当前状态是什么IRQL级别,可以通过`KeGetCurrtenIrql`函数获取当前IRQL级别。 + +然后驱动程序使用函数`KeRaiseIrql`将IRQL提高。它需要两个参数,第一个参数是提升后的级别,第二个参数保存提升前的级别。 + +最后,驱动程序在某个时刻需要将IRQL恢复到以前的级别,可以调用`KeLowerIrql`函数。 + +> NT内核在处理器的最高特权等级上执行,被设计为在并发的对称多处理器环境上执行。内核不能发生页面错误,因此内核所有的代码和数据总是驻留在系统内存时。而且不能被抢占,因此当处理器在执行属于内核的代码时不能发生上下文切换。但是,所有在任何处理器上执行的代码都是可以中断的,提供比正在执行的代码更高的中断等级就行。 + +``` + +`Windows Driver Development/9.0-文件系统的过滤.md`: + +```md +## 存储卷 store volume +存储设备可以称为一个卷,例如硬盘、软盘或CD-ROM,大卷可以分为多个逻辑卷,也被称为分区。 + +存储卷,是一个设备对象,通常是物理设备对象,表示系统的逻辑卷。存储设备对象驻留在存储设备堆栈中,但它不一定是堆栈中最顶层的设备对象。 + +当文件系统安装在存储卷上时,它会创建一个**文件系统卷设备对象(VDO)**来表示文件系统的卷。文件系统VDO通过称为volume parameter block(VPB)的共享对象安装在存储设备对象上。 + +### 卷名 +- 卷的设备对象名称,位于系统对象树的Device目录中,例如:"\Device\HarddiskVolume1" +- 卷的GUID +- 卷的符号链接名称,"\DosDevices\D:" + +## 卷的装载过程 +文件系统识别器 + +## 文件系统的设备对象 + +像FAT32、NTFS这样的文件系统,主要生成两类设备。首先文件系统驱动本身往往生成一个**控制设备(CDO)**,这个设备的主要任务是**修改整个驱动的内部配置**。因此,一般地说,一个文件系统只对应一个CDO。 + +另一种设备是**文件系统的卷设备**。 + +## 文件系统堆栈 +文件系统驱动创建两种不同类型的设备对象:控制设备对象(CDO)和卷设备对象(VDO)。文件系统堆栈由这些设备对象中的一个,连同用于附加到它的过滤驱动程序的对象构成。文件系统的设备对象始终构成堆栈的底部。 + +### 文件系统控制设备对象 +文件系统控制设备对象青整个文件系统,而不是单个卷,并存储在全局文件系统队列中。文件系统在其DriverEntry中创建一个或多个命名的控制设备对象。例如,FastFat创建了两个CDO:一个用于固定媒体,一个用于可移动媒体。CSFS只创建一个CDO,因为它只有可移动媒体。 + +文件系统控制设备对象需要命名。这是因为文件系统过滤驱动信赖卷设备对象和控制设备对象这种区别。 + +### 文件系统的卷设备对象 +文件系统卷设备对象表示由文件系统挂载的卷。文件系统在安装卷时会创建卷设备对象,通常是为了响应卷装入请求。与控制设备对象不同,卷设备对象始终与特定的逻辑或物理存储设备相关联。 + + + + + + + + + + + + + +IoGetDiskDeviceObject +The IoGetDiskDeviceObject routine retrieves a pointer to the **disk device object** associated with a given **file system volume device object**. + + +IoCreateStreamFileObjectLite +此函数会创建一个新的数据流文件的对象,但不会导致IRP_MJ_CLEANUP请求被发送至文件系统驱动栈。 + + +``` + +`Windows Driver Development/DPC函数.md`: + +```md +## Deferred Procedure Call 延时过程调用 + +如果逻辑上应该放在中断服务程序中完成的操作并非都是那么紧迫,其中一部分可能相对而言不那么紧迫,而又费时间,那么,这块可以在**放开中断**的情况下执行。 + +整个中断服务通常情况下都是在关中断的条件下执行的,如果把比较费时的操作都放在中断服务中执行,就会使关闭中断的时间太长而引起其它中断请求的丢失。 + +``` + +`Windows Driver Development/FsRtlEnterFileSystem.md`: + +```md +在做文件系统过滤驱动时,会经常用到FsRtlEnterFileSystem函数 + +   在什么地方调用? + +    每一个文件系统驱动例程在进行一个文件读写请求时,需要先调用FsRtlEnterFileSystem,同时在完成请求时,要立即调用FsRtlExitFileSystem。 + +    而文件系统过滤驱动基本上不用调用FsRtlEnterFileSystem,只有在你调用ExAcquireResourceExclusive(Lite)时,才需要调用,同时在调用完ExReleaseResource后,要立即调用FsRtlExitFileSystem。 + + 调用的目的? + +     把IRQL Level提高到 APC_LEVEL,来禁止一般的内核APC例程。 + + 注意事项? + +    需啊哟在IRQL PASSIVE_LEVEL级别上调用。 +--------------------- +作者:alwaysrun +来源:CSDN +原文:https://blog.csdn.net/alwaysrun/article/details/2290711 +版权声明:本文为博主原创文章,转载请附上博文链接! +``` + +`Windows Driver Development/IRP的同步和异步.md`: + +```md +## 同步和异步区别 +- 同步 + +![image](./images/1544435336(1).jpg) + +如上图,当程序调用DeviceIocontrol时,它的内部会创建一个`IRP_MJ_DEVICE_CONTROL`的IRP,这个IRP传送到驱动的派遣函数中,处理该IRP需要一段时间,直到该IRP处理完毕后,DeviceIoControl才会返回。 + +- 异步 + +![image](./images/1544436001(1).jpg) + +和同步不同的是,DeviceIoControl发送IRP到驱动后,不会等待该IRP结束,而是直接返回。当IRP经过一段时间被结束时,操作系统会触发一个IRP相关事件,这个事件可以通知应用程序IRP请求被执行完毕。 + + +## IRP的同步完成与异步完成 + +有两种处理IRP请求方式: +- 同步:在派遣函数中直接结束IRP请求 +- 异步:在派遣函数中不结束IRP请求,而是让派遣函数直接返回。IRP在以后的某个时候再进行外理。 + +### ReadFile的同步和异步 +- ReadFile的最后一个参数overlap,如果设置的话,会以异步读取 +- ReadFileEx异步 + +### IRP的异步完成 + +IRP的异步完成就是不在派遣函数中调用IoCompleteRequest函数。调用该函数意味着IRP请求的结束,也标志着本次对设备操作的结束。 + +- 首先使用`IoMarkIrpPending`函数将IRP设置为挂起。 +- 返回`STATUS_PENDING`状态 +- 保存这个IRP,因为要在别的时候结束它 +- 在其他地方处理这个IRP + + + +``` + +`Windows Driver Development/IoRegisterFsRegistrationChange.md`: + +```md +The `IoRegisterFsRegistrationChange` routine registers a file system filter driver's notification routine to be called whenever a file system registers or unregisters itself as an active file system. + + 大致意思为:`IoRegisterFsRegistrationChange`为文件系统过滤驱动注册一个通知例程,当文件系统注册或注销一个活动的文件系统时会被调用。 + +```C++ +NTSTATUS IoRegisterFsRegistrationChange( + PDRIVER_OBJECT DriverObject, + PDRIVER_FS_NOTIFICATION DriverNotificationRoutine +); +``` + +```C++ +//通知函数原型 +DRIVER_FS_NOTIFICATION DriverFsNotification; + +void DriverFsNotification( + _DEVICE_OBJECT *DeviceObject, + BOOLEAN FsActive +) +{...} +``` +IoRegisterFsRegistrationChange registers a file system filter driver to be notified whenever a file system calls IoRegisterFileSystem or IoUnregisterFileSystem. + +IoRegisterFsRegistrationChange注册文件系统过滤驱动程序,以便在文件系统调用IoRegisterFileSystem或IoUnregisterFileSystem时收到通知。 + +To stop receiving such notifications, the filter driver should call IoUnregisterFsRegistrationChange. + +当停止接收通知时,调用`IoUnregisterFsRegistrationChange` + +> 注意在Microsoft Windows XP及更高版本中,当文件系统过滤驱动程序调用IoRegisterFsRegistrationChange时,会立即为所有当前已注册的文件系统调用其通知例程(即,已调用IoRegisterFileSystem但尚未调用IoUnregisterFileSystem的文件系统)。 + +> 在IoRegisterFsRegistrationChange返回之前也可以调用通知例程,所以驱动程序必须创建了处理这些通知所需的相关数据结构之后才应调用此例程。 + +> 此外,在Windows XP及更高版本中,IoRegisterFsRegistrationChange会忽略RAW设备。 有关按名称附加到RAW文件系统的信息,请参阅将过滤器设备对象附加到目标设备对象。 + +`IoRegisterFsRegistrationChange`会增加过滤驱动程序的驱动对象的引用计数。 + +```C++ + +NTSTATUS +IoRegisterFsRegistrationChange( + IN PDRIVER_OBJECT DriverObject, + IN PDRIVER_FS_NOTIFICATION DriverNotificationRoutine + ) + +/*++ + +Routine Description: + + This routine registers the specified driver's notification routine to be + invoked whenever a file system registers or unregisters itself as an active + file system in the system. + +Arguments: + + DriverObject - Pointer to the driver object for the driver. + + DriverNotificationRoutine - Address of routine to invoke when a file system + registers or unregisters itself. + +Return Value: + + STATUS_DEVICE_ALREADY_ATTACHED - + Indicates that the caller has already registered + last with the same driver object & driver notification + + STATUS_INSUFFICIENT_RESOURCES + STATUS_SUCCESS + +--*/ + +{ + PNOTIFICATION_PACKET nPacket; + + PAGED_CODE(); + + ExAcquireResourceExclusiveLite( &IopDatabaseResource, TRUE ); + + if (!IsListEmpty( &IopFsNotifyChangeQueueHead )) { + + // + // Retrieve entry at tail of list + // + + nPacket = CONTAINING_RECORD( IopFsNotifyChangeQueueHead.Blink, NOTIFICATION_PACKET, ListEntry ); + + if ((nPacket->DriverObject == DriverObject) && + (nPacket->NotificationRoutine == DriverNotificationRoutine)) { + + ExReleaseResourceLite( &IopDatabaseResource); + return STATUS_DEVICE_ALREADY_ATTACHED; + } + } + + // + // Begin by attempting to allocate storage for the shutdown packet. If + // one cannot be allocated, simply return an appropriate error. + // + + nPacket = ExAllocatePoolWithTag( PagedPool|POOL_COLD_ALLOCATION, + sizeof( NOTIFICATION_PACKET ), + 'sFoI' ); + if (!nPacket) { + + ExReleaseResourceLite( &IopDatabaseResource ); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // + // Initialize the notification packet and insert it onto the tail of the + // list. + // + + nPacket->DriverObject = DriverObject; + nPacket->NotificationRoutine = DriverNotificationRoutine; + + InsertTailList( &IopFsNotifyChangeQueueHead, &nPacket->ListEntry ); + + IopNotifyAlreadyRegisteredFileSystems(&IopNetworkFileSystemQueueHead, DriverNotificationRoutine, FALSE); + IopNotifyAlreadyRegisteredFileSystems(&IopCdRomFileSystemQueueHead, DriverNotificationRoutine, TRUE); + IopNotifyAlreadyRegisteredFileSystems(&IopDiskFileSystemQueueHead, DriverNotificationRoutine, TRUE); + IopNotifyAlreadyRegisteredFileSystems(&IopTapeFileSystemQueueHead, DriverNotificationRoutine, TRUE); + + // + // Notify this driver about all already notified filesystems + // registered as an active file system of some type. + // + + + ExReleaseResourceLite( &IopDatabaseResource ); + + // + // Increment the number of reasons that this driver cannot be unloaded. + // + + ObReferenceObject( DriverObject ); + + return STATUS_SUCCESS; +} + + +VOID +IopNotifyAlreadyRegisteredFileSystems( + IN PLIST_ENTRY ListHead, + IN PDRIVER_FS_NOTIFICATION DriverNotificationRoutine, + IN BOOLEAN SkipRaw + ) +/*++ + +Routine Description: + + This routine calls the driver notification routine for filesystems + that have already been registered at the time of the call. + +Arguments: + + ListHead - Pointer to the filesystem registration list head. + DriverNotificationRoutine - Pointer to the routine that has to be called. + +Return Value: + + None. + +--*/ +{ + PLIST_ENTRY entry; + PDEVICE_OBJECT fsDeviceObject; + + entry = ListHead->Flink; + while (entry != ListHead) { + + // + // Skip raw filesystem notification + // + if ((entry->Flink == ListHead) && (SkipRaw)) { + break; + } + + fsDeviceObject = CONTAINING_RECORD( entry, DEVICE_OBJECT, Queue.ListEntry ); + entry = entry->Flink; + DriverNotificationRoutine( fsDeviceObject, TRUE ); + } +} + +``` +> `CONTAINING_RECORD`根据结构体中的某成员的地址来推算出该结构体整体的地址 + +由上源码可知,当调用`IoRegisterFsRegistrationChange`时,内部会对`IopNetworkFileSystemQueueHead`、`IopCdRomFileSystemQueueHead`、`IopDiskFileSystemQueueHead`、`IopTapeFileSystemQueueHead`这四个ListEntry遍历,它们记录了文件系统的设备。 + +``` + +`Windows Driver Development/IoSkipCurrentIrpStackLocation IoCopyCurrentIrpStackLocationToNext.md`: + +```md +# IoSkipCurrentIrpStackLocation IoCopyCurrentIrpStackLocationToNext 两个函数的区别 + +这两个函数都经常和 `IoCallDriver`函数配合使用 + + +看一下`IoCallDriver`的源码: + +```C +NTSTATUS +FASTCALL +IofCallDriver(IN PDEVICE_OBJECT DeviceObject, + IN PIRP Irp) +{ + PDRIVER_OBJECT DriverObject; + PIO_STACK_LOCATION StackPtr; + + /* Make sure this is a valid IRP */ + ASSERT(Irp->Type == IO_TYPE_IRP); + + /* Get the Driver Object */ + DriverObject = DeviceObject->DriverObject; + + /* Decrease the current location and check if */ + Irp->CurrentLocation--; + if (Irp->CurrentLocation <= 0) + { + /* This IRP ran out of stack, bugcheck */ + KeBugCheckEx(NO_MORE_IRP_STACK_LOCATIONS, (ULONG_PTR)Irp, 0, 0, 0); + } + + /* Now update the stack location */ + StackPtr = IoGetNextIrpStackLocation(Irp); + Irp->Tail.Overlay.CurrentStackLocation = StackPtr; + + /* Get the Device Object */ + StackPtr->DeviceObject = DeviceObject; + + /* Call it */ + return DriverObject->MajorFunction[StackPtr->MajorFunction](DeviceObject, + Irp); +} +``` + +其中的`CurrentLocation`和`CurrentStackLocation`都进行的自减 + + +## IoSkipCurrentIrpStackLocation + +```C +FORCEINLINE +VOID +IoSkipCurrentIrpStackLocation( + _Inout_ PIRP Irp) +{ + ASSERT(Irp->CurrentLocation <= Irp->StackCount); + Irp->CurrentLocation++; +#ifdef NONAMELESSUNION + Irp->Tail.Overlay.s.u.CurrentStackLocation++; +#else + Irp->Tail.Overlay.CurrentStackLocation++; +#endif +} +``` + +此时调用`IoCallDriver`函数时,相当于下层的驱动设备还是使用的当前的IRPStackLocation内容 + + +## IoCopyCurrentIrpStackLocationToNext + +```C +FORCEINLINE +VOID +IoCopyCurrentIrpStackLocationToNext( + _Inout_ PIRP Irp) +{ + PIO_STACK_LOCATION irpSp; + PIO_STACK_LOCATION nextIrpSp; + irpSp = IoGetCurrentIrpStackLocation(Irp); + nextIrpSp = IoGetNextIrpStackLocation(Irp); + RtlCopyMemory(nextIrpSp, irpSp, FIELD_OFFSET(IO_STACK_LOCATION, CompletionRoutine)); + nextIrpSp->Control = 0; +} +``` + +**注意:此函数拷贝的时候,只拷贝到了`CompletionRoutine`之前,下层的驱动设备使用的其实和当前的IRP是一样的,这样拷贝的作用是,当为当前设备设置完成函数时,不会影响到下层设备也被设置完成函数,如果将整个IRPStackLocation拷贝到下一层,那么下一层也会被设置完成函数,下层的完成函数可能会影响到IRP的完成。** + + +关于IrpStackLocation的讲解参阅 [分层驱动.md](./分层驱动.md) +``` + +`Windows Driver Development/Lookaside结构.md`: + +```md +# Lookaside结构 + +频繁申请和回收内存,会导致在内存上产生大量的内存“空洞”,从而导致最终无法申请内存。故提供Lookaside结构来解决这个问题。 + +如果驱动程序需要频繁地从内存中申请、回收固定大小的内存,DDK提供了一种机制来解决这个问题,这就是使用Lookaside对象。 + +可以将Lookaside对象想象成一个内存容器。在初始的时候,它先向Windows申请了一块比较大的内存。以后每次申请内存的时候,不是直接向Windows申请内存,而是向Lookaside对象申请内存。Lookaside对象会智能地避免产生内存“空洞”。如果Lookaside对象内部的内存不够用时,它会向操作系统申请更多的内存。当Lookaside对象内部有大量的未使用的内存时,它会自动让Windows回收一部分内存。 + + +使用Lookaside对象,首先要初始化: + + +``` + +`Windows Driver Development/MiniFilter通信.md`: + +```md +# MiniFilter 通信 + +Minifilter支持通过端口的用户模式和内核模式之间的通信。端口通信不进行缓冲,所以它更快更有效。 + +## MiniFilter中创建通信服务器端口 + +``` +NTSTATUS FLTAPI FltCreateCommunicationPort( + PFLT_FILTER Filter, + PFLT_PORT *ServerPort, + POBJECT_ATTRIBUTES ObjectAttributes, + PVOID ServerPortCookie, + PFLT_CONNECT_NOTIFY ConnectNotifyCallback, + PFLT_DISCONNECT_NOTIFY DisconnectNotifyCallback, + PFLT_MESSAGE_NOTIFY MessageNotifyCallback, + LONG MaxConnections +); +``` + +此函数创建一个可接收从用户模式的连接请求的通信服务器端口。 + +当用户模式调用者尝试连接到端口时,MiniFilter会调用`ConnectNotifyCallback`函数创建新的连接。 + + +**当使用`FltCloseCommunicationPort`关闭服务器端口时,任何新的连接都是不允许的,但是,现有的连接仍然保持连接,直到它们被用户模式应用程序或MiniFilter关闭,或MiniFilter驱动卸载。** + +### 参数 + +- Filter + +MiniFilter的句柄 + +- ServerPort + +用来返回用于通信的服务器端口。MiniFilter使用该句柄来监听来自用户模式的连接请求 + +- ObjectAttributes + +使用`InitializeObjectAttributes`初始化 + +- ServerPortCookie + +一般为NULL + +- ConnectNotifyCallback + +每当一个用户模式应用程序调用`FilterConnectCommunicationPort`发送连接请求时,MiniFilter会调用这个回调函数。不可为NULL + +- DisconnectNotifyCallback + +当客户端连接数达到0时,或MiniFilter要卸载时,MiniFilter会调用此回调函数。不可为NULL + +- MessageNotifyCallback + +每当用户模式应用程序调用`FilterSendMessage`将消息发送到MiniFilter时,MiniFilter会调用此函数。可为NULL + +- MaxConnections + +允许客户端同时连接这个服务器端口的最大数目。此参数必需的,且必须大于0. + +## 用户模式连接端口 + +``` +HRESULT FilterConnectCommunicationPort( + LPCWSTR lpPortName, + DWORD dwOptions, + LPCVOID lpContext, + WORD wSizeOfContext, + LPSECURITY_ATTRIBUTES lpSecurityAttributes, + HANDLE *hPort +); +``` + +### 参数 + +- lpPortName + +指向包含通信服务器端口的字符串,如:`L"\\MyFilterPort"` + +- dwOptions + +一般为0 + +- lpContext + +一般为NULL + +- wSizeOfContext + +一般为NULL + +- lpSecurityAttributes + +一般为NULL + +- hPort + +用来返回新创建的连接的句柄 + + + +## 通信 + +### MiniFilter框架中 + +#### 收消息 + +当用户模式发送消息时,MiniFilter中`MessageNotifyCallback`回调函数会收到消息。 + +#### 发消息 + +`FltSendMessage`向一个**正在等待**消息的用户模式应用程序发送消息 + +``` +NTSTATUS FLTAPI FltSendMessage( + PFLT_FILTER Filter, + PFLT_PORT *ClientPort, + PVOID SenderBuffer, + ULONG SenderBufferLength, + PVOID ReplyBuffer, + PULONG ReplyLength, + PLARGE_INTEGER Timeout +); +``` + +参数`ClientPort`从`ConnectNotifyCallback`中获取 + +**`Timeout`表示等待时间,直到用户模式应用程序接收到消息并且返回一个回复,如果设置为NULL,则无限制等待。当`ReplyBuffer`和`Timeout`为空时,应用层用户程序接收到消息后,`FltSendMessage`会立刻返回`STATUS_SUCCESS`。** + +**当`ReplyBuffer`不为空时,应用层使用`FilterGetMessage`接收到消息后,应用层用户程序必须使用`FilterReplyMessage`向Minifilter回复一个信息,当`FltSendMessage`接收到这个信息后,函数返回`STATUS_SUCCESS`。** + +### 应用层用户程序 + +#### 收消息 + +使用`FilterGetMessage`和`FilterReplyMessage`函数 + +``` +HRESULT FilterGetMessage( + HANDLE hPort, + PFILTER_MESSAGE_HEADER lpMessageBuffer, + DWORD dwMessageBufferSize, + LPOVERLAPPED lpOverlapped +); +``` + +``` +HRESULT FilterReplyMessage( + HANDLE hPort, + PFILTER_REPLY_HEADER lpReplyBuffer, + DWORD dwReplyBufferSize +); +``` + +**注意:`FilterGetMessage`的buffer结构体中需使用`FILTER_MESSAGE_HEADER`结构,`FilterReplyMessage`的buffer结构体中需使用`FILTER_REPLY_HEADER`结构。何时需要reply见上文。** + + +#### 发消息 + +``` +HRESULT FilterSendMessage( + HANDLE hPort, + LPVOID lpInBuffer, + DWORD dwInBufferSize, + LPVOID lpOutBuffer, + DWORD dwOutBufferSize, + LPDWORD lpBytesReturned +); +``` + +使用此函数发消息时,MiniFilter中接收消息的为`MessageNotifyCallback`函数 + + +## 关闭 + +MiniFilter中使用`FltCloseClientPort`函数,每个连入Minifilter的应用层通信可在`ConnectNotifyCallback`函数中获得端口。 + +应用层用户程序使用`CloseHandle`函数。 +``` + +`Windows Driver Development/MiniFilter驱动.md`: + +```md +## MiniFilter驱动加载顺序 + +每个minifilter驱动程序必须具有称为`altitude`的唯一标识符。 +此值在驱动的inf文件中定义,例如:Instance1.Altitude = "370030" + +当加载微过滤器驱动程序时,微过滤器驱动程序的高度定义其相对于I/O堆栈中其他微过滤器驱动程序的位置。高度是一个无限精度的字符串,被解释为十进制数字。具有较低数值高度的微过滤器驱动器被加载到具有较高数值的微过滤器驱动器下方的I/O堆栈中。 + +高度越低,加载越早。 + +下图显示了带有过滤器管理器和三个微过滤器驱动程序的简化I/O堆栈: + +![image](./images/1548818559(1).jpg) + +微过滤器驱动程序可以过滤基于IRP的I/O操作以及快速I/O和文件系统过滤器回调操作。对于它选择过滤的每个I/O操作,minifilter驱动程序可以注册操作前回调例程,后操作回调例程或两者。 + +假设上图中的所有三个微过滤器驱动程序都注册了相同的I/O操作,过滤器管理器将按照从高到低(A,B,C)的高度顺序调用其操作前回调例程,然后转发I/O请求下一个较低的驱动程序进行进一步处理。当过滤器管理器收到完成的I/O请求时,它会以相反的顺序从最低到最高(C,B,A)调用每个minifilter驱动程序的后操作回调例程。 + +## Minifilter的启动和停止 + +在系统运行时,可以通过服务启动请求: + +``` +sc start + +net start +``` +或通过显式加载请求 +``` +fltmc load +``` + +加载minifilter驱动程序时会调用minifilter驱动程序的DriverEntry例程,因此minifilter驱动程序可以执行初始化,该初始化将应用于minifilter驱动程序的所有实例。在其DriverEntry例程中,minifilter驱动程序调用`FltRegisterFilter`向过滤管理器注册回调例程,和`FltStartFilter`通知过滤管理器minifilter驱动程序已准备好开始附加到卷并过滤I/O请求。 + + +在挂载卷后的第一个创建操作上调用其`InstanceSetupCallBack`例程,自动通知微过滤器驱动程序有关可用卷的信息。 + +``` +fltmc detach +``` +停止过程如下: + +- 如果minifilter驱动程序注册了`InstanceTeardownStartCallback`回调例程,则过滤管理器会在卸载过程开始时调用它 +- 在卸载期间,任何当前正在执行的操作前或操作后回调例程继续正常处理,等待后操作回调的任何I/O请求可以被排空或取消,并且由微过滤器驱动程序生成的任何I/O请求继续正常处理直到他们完成。 +- 如果minifilter驱动程序注册了`InstanceTeardownCompleteCallback`例程,则过滤管理器会在完成所有未完成的I/O操作后调用此例程。在此例程中,minifilter驱动程序将关闭所有仍处于打开状态的文件。 +- 释放所有对实例的未完成引用后,过滤管理器将删除剩余的上下文,并且实例将完全拆除。 + + +在系统运行时,可以通过服务停止请求: +``` +sc stop +net stop +``` + +或 +通过显式卸载请求 +``` +fltmc unload +``` +卸载minifilter请求。 + +卸载minifilter驱动程序时会调用minifilter驱动程序的`FilterUnloadCallback`例程。此例程关闭所有打开的通信服务器端口,调用`FltUnregisterFilter`,并执行任何所需的清理。注册此例程是可选的,但是,如果minifilter驱动程序未注册此例程,则无法卸载minifilter驱动。 + +**MiniFilter注册相关代码实现** +``` +// 此段代码应该放在驱动初始化DriverEntry处。 +status = FltRegisterFilter(DriverObject, &FilterRegistration, &g_FilterHandle); //注册过滤器 + +if (NT_SUCCESS(status)) +{ + status = FltStartFiltering(g_FilterHandle); + + if (!NT_SUCCESS(status)) + { + KdPrint(("MiniFilter注册失败\n")); + FltUnregisterFilter(g_FilterHandle); + } +} + +``` + +``` +// minifilter反注册放在minifilter卸载回调函数中 +NTSTATUS + MiniFilterUnload( + _In_ FLT_FILTER_UNLOAD_FLAGS Flags + ) +{ + //卸载回调函数 + FltUnregisterFilter(g_FilterHandle); + return STATUS_SUCCESS; +} +``` + + +### FltRegisterFilter函数注册了什么? + +以下是API的声明: + +``` +NTSTATUS FLTAPI FltRegisterFilter( + PDRIVER_OBJECT Driver, + const FLT_REGISTRATION *Registration, + PFLT_FILTER *RetFilter +); +``` + +注意第二个参数`FLT_REGISTERATION`结构体。 + +``` +typedef struct _FLT_REGISTRATION { + USHORT Size; + USHORT Version; + FLT_REGISTRATION_FLAGS Flags; + const FLT_CONTEXT_REGISTRATION *ContextRegistration; + const FLT_OPERATION_REGISTRATION *OperationRegistration; + PFLT_FILTER_UNLOAD_CALLBACK FilterUnloadCallback; + PFLT_INSTANCE_SETUP_CALLBACK InstanceSetupCallback; + PFLT_INSTANCE_QUERY_TEARDOWN_CALLBACK InstanceQueryTeardownCallback; + PFLT_INSTANCE_TEARDOWN_CALLBACK InstanceTeardownStartCallback; + PFLT_INSTANCE_TEARDOWN_CALLBACK InstanceTeardownCompleteCallback; + PFLT_GENERATE_FILE_NAME GenerateFileNameCallback; + PFLT_NORMALIZE_NAME_COMPONENT NormalizeNameComponentCallback; + PFLT_NORMALIZE_CONTEXT_CLEANUP NormalizeContextCleanupCallback; + PFLT_TRANSACTION_NOTIFICATION_CALLBACK TransactionNotificationCallback; + PFLT_NORMALIZE_NAME_COMPONENT_EX NormalizeNameComponentExCallback; + PFLT_SECTION_CONFLICT_NOTIFICATION_CALLBACK SectionNotificationCallback; +} FLT_REGISTRATION, *PFLT_REGISTRATION; +``` + +此结构体包含了minifilter所用到的回调函数,除了`size`和`version`参数不为空且固定以外,其余参数皆可为NULL。 + +- size处固定填`sizeof(FLT_REGISTRATION)` +- version处固定填`FLT_REGISTRATION_VERSION` +- Flags处一般为NULL +- ContextRegistration处是一个FLT_CONTEXT_REGISTRATION结构的变长数组,用于标明minifilter使用的上下文类型。数组必须以`{FLT_CONTEXT_END}`结尾,在分配上下文时会使用到。示例如下: + ``` + CONST FLT_CONTEXT_REGISTRATION ContextNotifications[] = { + + { FLT_VOLUME_CONTEXT, + 0, + CleanupVolumeContext, + sizeof(VOLUME_CONTEXT), + FILE_DISK_POOL_TAG }, + + { FLT_STREAMHANDLE_CONTEXT, + 0, + NULL, + sizeof(SCANNER_STREAM_HANDLE_CONTEXT), + FILE_DISK_POOL_TAG }, + + { FLT_CONTEXT_END } + }; + ``` +- OperationRegistration是一个`FLT_OPERATION_REGISTRATION`结构的变长数组,每个IRP请求类型对应一个,minifilter为它注册预操作和后操作。数组中的最后一个元素必须以`{IRP_MJ_OPERATION_END}`结尾,示例如下: + ``` + CONST FLT_OPERATION_REGISTRATION Callbacks[] = { + + #if 0 // TODO - List all of the requests to filter. + { IRP_MJ_CREATE, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_CREATE_NAMED_PIPE, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_CLOSE, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_READ, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_WRITE, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_QUERY_INFORMATION, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_SET_INFORMATION, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_QUERY_EA, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_SET_EA, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_FLUSH_BUFFERS, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_QUERY_VOLUME_INFORMATION, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_SET_VOLUME_INFORMATION, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_DIRECTORY_CONTROL, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_FILE_SYSTEM_CONTROL, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_DEVICE_CONTROL, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_INTERNAL_DEVICE_CONTROL, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_SHUTDOWN, + 0, + FsFilter1PreOperationNoPostOperation, + NULL }, //post operations not supported + + { IRP_MJ_LOCK_CONTROL, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_CLEANUP, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_CREATE_MAILSLOT, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_QUERY_SECURITY, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_SET_SECURITY, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_QUERY_QUOTA, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_SET_QUOTA, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_PNP, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_ACQUIRE_FOR_SECTION_SYNCHRONIZATION, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_RELEASE_FOR_SECTION_SYNCHRONIZATION, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_ACQUIRE_FOR_MOD_WRITE, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_RELEASE_FOR_MOD_WRITE, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_ACQUIRE_FOR_CC_FLUSH, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_RELEASE_FOR_CC_FLUSH, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_FAST_IO_CHECK_IF_POSSIBLE, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_NETWORK_QUERY_OPEN, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_MDL_READ, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_MDL_READ_COMPLETE, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_PREPARE_MDL_WRITE, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_MDL_WRITE_COMPLETE, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_VOLUME_MOUNT, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + { IRP_MJ_VOLUME_DISMOUNT, + 0, + FsFilter1PreOperation, + FsFilter1PostOperation }, + + #endif // TODO + + { IRP_MJ_OPERATION_END } + }; + ``` +- FilterUnloadCallback注册过滤器的卸载例程,如果为NULL的话,则minifilter不能卸载。 +- InstanceSetupCallback是minifilter安装的回调例程,每个卷要加载都会经过此例程的处理。 +- 其余参数用的不多,自己也不太了解 + + +## MiniFilter通信 + +minifilter通过`通信端口`进行用户模式和内核模式之间的通信。 + +### 内核模式 + +- FltCloseClientPort +- FltCloseCommunicationPort +- FltCreateCommunicationPort +- FltSendMessage + +#### 创建通信端口 + +拿下面代码作讲解: + +``` + status = RtlCreateSecurityDescriptor(&miniFltSd, SECURITY_DESCRIPTOR_REVISION); + + if (!NT_SUCCESS(status)) { + return status; + } + + RtlSetDaclSecurityDescriptor(&miniFltSd, TRUE, NULL, FALSE); + + RtlInitUnicodeString(&uniString, MINISPY_PORT_NAME); + + InitializeObjectAttributes(&miniFltOa, + &uniString, + OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, + NULL, + &miniFltSd); + + + + status = FltCreateCommunicationPort(g_FilterHandle, + &g_ServerPort, + &miniFltOa, + NULL, + FDMiniConnect, + FDMiniDisconnect, + FDMiniMessage, + 50); //修改客户端最大连接数 + + if (!NT_SUCCESS(status)) { + + if (NULL != g_ServerPort) { + FltCloseCommunicationPort(g_ServerPort); + } + + if (NULL != g_FilterHandle) { + FltUnregisterFilter(g_FilterHandle); + } + } +``` +这一套就是一个固定的流程,着重看一下`FltCreateCommunicationPort`这个函数 + +``` +NTSTATUS FLTAPI FltCreateCommunicationPort( + PFLT_FILTER Filter, + PFLT_PORT *ServerPort, + POBJECT_ATTRIBUTES ObjectAttributes, + PVOID ServerPortCookie, + PFLT_CONNECT_NOTIFY ConnectNotifyCallback, + PFLT_DISCONNECT_NOTIFY DisconnectNotifyCallback, + PFLT_MESSAGE_NOTIFY MessageNotifyCallback, + LONG MaxConnections +); +``` +- Filter 这里填`FltRegisterFilter`返回的过滤器的句柄 +- ServerPort 这里生成一个用于通信的端口 +- ObjectAttributes 指向`OBJECT_ATTRIBUTES`结构的指针 +- ConnectNotifyCallback 当用户模式应用程序向驱动发送连接请求时,minifilter就会调用此例程 +- DisconnectNotifyCallback 当用户态与内核态连接结束时,minifilter会调用此例程 +- MessageNotifyCallback 当用户态与内核态传送数据时,用户模式程序调用`FilterSendMessage`通过客户端端口向minifilter驱动程序发送消息,minifilter会调用此例程 +- MaxConnections 此端口允许的最大并发客户端连接数,此参数必需,且必须大于0 + +详细了解一下接收消息,该函数定义如下: +``` +typedef NTSTATUS +(*PFLT_MESSAGE_NOTIFY) ( + IN PVOID PortCookie, + IN PVOID InputBuffer OPTIONAL, + IN ULONG InputBufferLength, + OUT PVOID OutputBuffer OPTIONAL, + IN ULONG OutputBufferLength, + OUT PULONG ReturnOutputBufferLength + ); +``` +- InputBuffer 是接收到的缓冲区 +- InputBufferLength 接收到的缓冲区长度 +- OutputBuffer 如果minifilter需要回复数据,**注意,OutputBuffer是指向用户模式缓冲区的指针,此指针仅在用户模式过程的上下文中有效,并且只能从try/except块中访问。minifilter调用`ProbeForWrite`来验证这个指针,但是他不能确保缓冲区正确对齐。如果缓冲区包含具有对齐要求的结构,minifilter驱动程序负责执行任何必要的对齐检查。为此,minifilter驱动程序可以使用`IS_ALIGNED`宏。** +- OutputBufferLength OutputBuffer指向缓冲区的大小 +- ReturnOutputBufferLength 接收OutputBuffer指向的缓冲区中返回的字节数 + + +#### 发送消息 + +minifilter驱动程序使用`FltSendMessage`例程向等待的用户模式应用程序发送消息 + +``` +NTSTATUS FLTAPI FltSendMessage( + PFLT_FILTER Filter, + PFLT_PORT *ClientPort, + PVOID SenderBuffer, + ULONG SenderBufferLength, + PVOID ReplyBuffer, + PULONG ReplyLength, + PLARGE_INTEGER Timeout +); +``` + +> - ClientPort 指向连接端口的指针,此参数详情看`FltCreateCommunicationPort`函数的`ConnectNotifyCallback`项,该函数指针中的有一个ClientPort参数。 +> - SenderBuffer 指向缓冲区的指针,该缓冲区包含要发送到用户模式程序的消息。 +> - SenderBufferLength SenderBuffer指向缓冲区的大小 +> - ReplyBuffer 指向缓冲区的指针,该缓冲区从用户模式程序接收回复。 +> - ReplyBufferLength ReplyBuffer指向缓冲区的大小 +> - Timeout 指向超时时间的指针,时间单位为100纳秒,在应用程序接收到消息之前,驱动程序会进入等待状态,直到在给定的时间内收到回复,如果设置为NULL的话,则minifilter会进入无限等待状态 + +当minifilter驱动程序向用户模式应用程序使用`FltSendMessage`发送消息后,如果应用程序调用`FilterGetMessage`来获取消息,则minifilter驱动程序会立即传递消息。在传递消息后,如果`ReplyBuffer`为`NULL`,则`FltSendMessage`返回`STATUS_SUCCESS`。否则,如果`Timeout`非零,则minifilter会进入等待状态: +- 如果应用程序在超时间隔到期之前调用`FilterReplyMessage`,则minifilter驱动程序将收到回复,并且`FltSendMessage`将返回`STATUS_SUCCESS` +- 否则,minifilter驱动程序不会收到回复,`FltSendMessage`将返回`STATUS_TIMEOUT` + +其中用户模式的函数详见[用户模式](###用户模式) + + +### 用户模式 + +- FilterConnectCommunicationPort +- FilterGetMessage + + ``` + HRESULT FilterGetMessage( + HANDLE hPort, + PFILTER_MESSAGE_HEADER lpMessageBuffer, + DWORD dwMessageBufferSize, + LPOVERLAPPED lpOverlapped + ); + ``` + - hPort 调用`FilterConnectCommunicationPort`返回的通信端口句柄。 + - lpMessageBuffer 指向缓冲区的指针,该缓冲区接收来自minifilter的消息。消息必须包含`FILTER_MESSAGE_HEADER`结构。 + + +- FilterReplyMessage + + ``` + HRESULT FilterReplyMessage( + HANDLE hPort, + PFILTER_REPLY_HEADER lpReplyBuffer, + DWORD dwReplyBufferSize + ); + ``` + - lpReplyBuffer 指向缓冲区的指针,该缓冲区包含要发送给minifilter的回复。消息必须包含`FILTER_REPLY_HEADER`结构 + +- FilterSendMessage + + ``` + HRESULT FilterSendMessage( + HANDLE hPort, + LPVOID lpInBuffer, + DWORD dwInBufferSize, + LPVOID lpOutBuffer, + DWORD dwOutBufferSize, + LPDWORD lpBytesReturned + ); + ``` + + + +## 重要函数 + +1. FltGetDiskDeviceObject +返回一个与给定的卷相关联的磁盘设备对象 +``` +NTSTATUS FLTAPI FltGetDiskDeviceObject( + PFLT_VOLUME Volume, + PDEVICE_OBJECT *DiskDeviceObject +); +``` +> 此函数增加了`*DiskDeviceObject`中返回的设备对象指针的引用计数。当不再需要此指针时,调用者必须通过调用`ObDereferenceObject`来减少此引用计数。如果不这样做,则会导致系统释放或删除设备对象失败。 + +2. FltGetDeviceObject +返回一个指向给定卷的过滤管理器的卷设备对象(The FltGetDeviceObject routine returns a pointer to the Filter Manager's volume device object (VDO) for a given volume.) +``` +NTSTATUS FLTAPI FltGetDeviceObject( + PFLT_VOLUME Volume, + PDEVICE_OBJECT *DeviceObject +); +``` +> 此函数在使用过后,如不再使用DeviceObject,也需要调用`ObDereferenceObject`来减少引用计数。 + +3. FltFlushBuffers +minifilter给指定的文件刷新缓存 + +4. FltQueryInformationFile +获取指定文件的信息 + +``` + +`Windows Driver Development/MiniFilter驱动隐藏文件夹的实现.md`: + +```md +利用驱动实现文件的隐藏主要是在IRP_MJ_DIRECTORY_CONTROL的PostOperation回调中处理其参数`FLT_CALLBACK_DATA`结构中的缓冲区数据。 + +``` +Data->Iopb->Parameters.DirectoryControl.QueryDirectory.DirectoryBuffer(或MdlAddress) +``` + +``` +Data->Iopb->Parameters.DirectoryControl.QueryDirectory.FileInformationClass +``` +> Vista或Win7返回的FileInformationClass结构不再是FileBothDirectoryInformation。而是FileidBothDirectoryInformation + +对需要隐藏的文件夹进行断链操作: +``` +//大致代码如下: +int modified = 0; +int removedAllEntries = 1; + +SafeBuffer = Data->Iopb->Parameters.DirectoryControl.QueryDirectory.DirectoryBuffer; + +currentFileInfo = (PFILE_ID_BOTH_DIR_INFORMATION)SafeBuffer; + +previousFileInfo = currentFileInfo; + +do +{ + nextOffset = currentFileInfo->NextEntryOffset; + + if(wcscmp(currentFileInfo->FileName, HideFileName) == 0) + { + if(nextOffset == 0) + { + previousFileInfo->NextEntryOffset = 0; + } + else + { + previousFileInfo->NextEntryOffset = (ULONG)((PCHAR)currentFileInfo - (PCHAR)previousFileInfo) + nextOffset; + } + modified = 1; + } + else + { + previousFileInfo = currentFileInfo; + } + currentFileInfo = currentFileInfo; +}while(nextOffset != 0); + +if( modified ) +{ + if( removedAllEntries ) + { + Data->IoStatus.Status = STATUS_NO_MORE_FILES; + } + else + { + FltSetCallbackDataDirty( Data ); + } +} + +``` + +``` + +`Windows Driver Development/PNP.md`: + +```md +即插即用IPR IRP_MJ_PNP + +所有的即插即用的事件,都会引发一个即插即用管理器向WDM驱动发送一个IPR_MJ_PNP,IRP_MJ_PNP是这个IRP的主功能代码,不同的即插即用事件会有不同的子功能代码。 + + +## 通过设备接口寻找设备 + +### 设备接口 + +在WDM驱动中,一般很少用到设备名,也很少用到符号链接,而是用一个设备接口来指定设备。设备接口其实就是一组全局标识,它是一个128位组成的数字,并能保证在全世界范围内不会产生冲突。 + +**引入设备接口的主要原因是避免设备名冲突,例如不同的网卡厂商可能都将自己的设备名命名为:NetCardDevice,当用户同时插入两块网卡时,就会引起冲突。** + + +### WDM驱动中设置接口 + +在WDM驱动程序中使用设备接口,首先在创建设备的时候,不能指定设备名,这样系统会为设备自动创建一个设备名,该设备名就是一个数字,新设备加入的时候,数字会依次递增。 + + +另外不用IoCreateSymbolicLink创建符号链接,而是用IoRegisterDeviceInterface为设备创建设备链接。 + +当一个设备被添加到系统时,Windows查找正确的驱动程序,并调用它的DriverEntry例程。PNP管理器然后调用驱动程序的AddDevice例程,告诉它添加了一个新设备。此时程序创建自己的设备对象,即FDO。 + + + +``` + +`Windows Driver Development/StartIO.md`: + +```md +# StartIO例程 + +StartIO例程能够保证各个并行的IRP顺序执行,即串行化。 + +## 并行执行与串行执行 +在很多情况时,对设备的操作必须是串行执行,而不能并行执行。例如,对串口的操作,假如有N个线程同时操作串口设备时,必须将这些操作排队,然后一一进行处理。如果不做串行化处理,当一个操作没有完毕时,新的操作又开始,这必然会导致操作的混乱。 + +当一个新的IRP请求来临时,首先检查设备是否处于“忙”状态。设备在初始化的时候为“空闲”状态。当设备处在“空闲”的时候,可以处理一个IRP请求,并改变当前设备状态为“忙”状态。如果设备处于“忙”状态,则将新来的IRP插入队列,并立刻返回,IRP留在以后处理。 + +当设备状态由“忙”转入“空闲”状态时,则从队列取出一个IRP进行处理,并重新将状态变为“忙”。这样,周而复始地将所有IRP的请求串行都处理了。 + +## StartIO例程 + +操作系统提供了一个IRP队列来实现串行,这个队列的队列头保存在设备对象的`DeviceObject->DeviceQueue`子域中。**插入和删除队列中的元素都是操作系统负责的。在使用这个队列的时候,需要向系统提供一个叫做StartIo的例程**。 + +``` +DriverObject->DriverStartIO = HelloDDKStartIO; +``` + +**注意StartIO是执行在`DISPATCH_LEVEL`级别上,因此声明如下:** + +``` +#pragma LOCKEDCODE +VOID HelloDDKStartIO( +IN PDEVICE_OBJECT DeviceObject, +IN PRIP Irp) +{ +... +} +``` + +``` + +`Windows Driver Development/WFP/FWPS_INCOMING_VALUES0.md`: + +```md +# FWPS_INCOMING_VALUES0 + +## 结构 + +```C++ +typedef struct FWPS_INCOMING_VALUES0_ { + UINT16 layerId; + UINT32 valueCount; + FWPS_INCOMING_VALUE0 *incomingValue; +} FWPS_INCOMING_VALUES0; +``` + +**layerId**: 指向的是过滤分层标识 + +**valueCount**: 表示下面`FWPS_INCOMING_VALUE0`数组元素大小。 + +**incomingValue**: 具体的包含什么数据,查看[官方解释](https://docs.microsoft.com/en-us/windows-hardware/drivers/network/data-field-identifiers) + +```C++ +typedef struct FWP_VALUE0_ { + FWP_DATA_TYPE type; + union { + UINT8 uint8; + UINT16 uint16; + UINT32 uint32; + UINT64 *uint64; + INT8 int8; + INT16 int16; + INT32 int32; + INT64 *int64; + float float32; + double *double64; + FWP_BYTE_ARRAY16 *byteArray16; + FWP_BYTE_BLOB *byteBlob; + SID *sid; + FWP_BYTE_BLOB *sd; + FWP_TOKEN_INFORMATION *tokenInformation; + FWP_BYTE_BLOB *tokenAccessInformation; + LPWSTR unicodeString; + FWP_BYTE_ARRAY6 *byteArray6; + }; +} FWP_VALUE0; +``` +``` + +`Windows Driver Development/WFP/WFP.md`: + +```md +# WFP (Windows Filtering Platform) + +## 核心 + +> 内核态过滤引擎是整个WFP框架的核心。内核态过滤引擎需要和系统网络协议栈交互,通过一种称之为“垫片”的内核模块从网络协议栈中获取网络数据,垫片被插入到网络协议栈各个层中。垫片获取到数据后,通过内核态过滤引擎(KM Filter Engine)提供的分类API(Classify API),把数据传送到WFP的相应分层(Layer)中。 + +> KM Filter Engine 被划分中若干个分层,在每个分层中,可能会存在一个或多个子层,子层是更小的划分,它们被赋予了不同的权重(weight),在同一个层中,WFP按照子层权重的大小顺序,从大到小把数据交给相应的子层。 + +WFP中还包含一个重要的数据结构,被称为过滤器(Filter),过滤器中保存了网络数据包的拦截规则和处理动作。 + +## 垫片 + +垫片除了能从网络协议栈获取网络数据,还胡更重要的一个作用是把内核态过滤引擎的过滤结果反馈给网络网络协议栈。 + +垫片负责WFP的数据来源以及执行数据拦截/放行的最终动作,属于网络协议栈和WFP框架之间的通信桥梁。 + +## Callout接口(呼出接口) + +> A callout provides functionality that extends the capabilities of the Windows Filtering Platform. + +以上为官方解释,callout扩展WFP功能。故用`呼出接口`来称呼不合适,callout可解释成`call-out`调用外部。 + +Callout 由一组 `Callout Function`和一个用来识别一个Callout的`GUID`值 + +系统内置了几个Callout以供使用,也可以自己通过callout driver添加Callout。 + +当过滤器关联一个Callout后 + +- 如果规则被命中时,过滤引擎会回调Callout的`classifyFn`函数。 + +- 当过滤器被添加到过滤引擎中或者从过滤引擎中移除时,WFP会调用这个callout的`notifyFn1`函数。(主要是看是否承认这个事实) + +- 当一个网络数据流将要被终止时,WFP会调用`flowDeleteFn`函数。但是,WFP调用flowDeleteFn是有条件的,只有在这个将要终止的数据流被关联了上下文的情况下,flowDeleteFn都会被调用。 + +### Callout注册与卸载 + +注册Callout可以使用`FwpsCalloutRegister0`(Vista开始支持)、`FwpsCalloutRegister1(Win7)`、`FwpsCalloutRegister2(Win8)` + +卸载使用: + +- `FwpsCalloutUnregisterById0` 使用的是运行注册函数返回的calloutid + +- `FwpsCalloutUnregisterByKey0` 使用的是创建callout结构体时填充的calloutkey + +### Callout的添加与移除 + +成功注册callout后,还需要把callout添加到过滤引擎中。 + +添加之前首先要(`FwpmEngineOpen`)打开过过滤引擎,主要是获取引擎句柄。 + +使用`FwpmCalloutAdd`添加。 + +## 分层 + +分层是一个**容器**,里面包含了零个或多个过滤器。此外,分层内部也可能包含一个或多个子层。 + +### 子层 + +使用`FwpmSubLayerAdd`添加子层。 + +管理过滤分层标识符:https://docs.microsoft.com/zh-cn/windows-hardware/drivers/network/management-filtering-layer-identifiers + +## 过滤器 + +**过滤器存在于WFP的分层中。** + +> 过滤器将子层和callout连接在一起。 + +过滤器是`规则`和`动作`的集合 + +- 规则: 指明了对哪些网络数据包感兴趣。它实际上是过滤条件(condition),一个过滤器里面包含一个或多个过滤条件。当所有的条件命中时,才认为规则成立。一旦规则命中,则会执行指定的动作。 +- 动作: 拦截/放行 + +一个分层中可以存在多个过滤器,不同的过滤器被赋予不同的权重。 + +过滤器定义了几种用于过滤TCP / IP网络数据的过滤条件,如果所有过滤条件都为真,则对数据采取的操作。 如果过滤器需要对网络数据进行额外处理,则可以为过滤器的操作指定Callout。 如果此类过滤器的过滤条件都为真,则过滤器引擎将网络数据传递到指定的Callout以进行其他处理。 + +### 过滤器添加 + +使用`FwpmFilterAdd`添加。 + +## 官方说明 + +https://docs.microsoft.com/en-us/windows-hardware/drivers/network/windows-filtering-platform-callout-drivers2 + + +``` + +`Windows Driver Development/WFP/分层标识.md`: + +```md +# Filtering Layer Identifiers +05/31/2018 +9 minutes to read +The Windows Filtering Platform (WFP) layer identifiers are each represented by a GUID. These identifiers are defined as follows. + +The V4 and V6 suffixes at the end of the layer identifiers indicate whether the layer is located in the IPv4 network stack or in the IPv6 network stack. + +FWPM_LAYER_INBOUND_IPPACKET_V4 / FWPM_LAYER_INBOUND_IPPACKET_V6 + +This filtering layer is located in the receive path just after the IP header of a received packet has been parsed but before any IP header processing takes place. No IPsec decryption or reassembly has occurred. + +FWPM_LAYER_INBOUND_IPPACKET_V4_DISCARD / FWPM_LAYER_INBOUND_IPPACKET_V6_DISCARD + +This filtering layer is located in the receive path for inspecting any received packets that have been discarded at the network layer. + +FWPM_LAYER_OUTBOUND_IPPACKET_V4 / FWPM_LAYER_OUTBOUND_IPPACKET_V6 + +This filtering layer is located in the send path just before the sent packet is evaluated for fragmentation. All IP header processing is complete and all extension headers are in place. Any IPsec authentication and encryption has already occurred. + +FWPM_LAYER_OUTBOUND_IPPACKET_V4_DISCARD / FWPM_LAYER_OUTBOUND_IPPACKET_V6_DISCARD + +This filtering layer is located in the send path for inspecting any sent packets that have been discarded at the network layer. + +FWPM_LAYER_IPFORWARD_V4 / FWPM_LAYER_IPFORWARD_V6 + +This filtering layer is located in the forwarding path at the point where a received packet is forwarded. + +FWPM_LAYER_IPFORWARD_V4_DISCARD / FWPM_LAYER_IPFORWARD_V6_DISCARD + +This filtering layer is located in the forwarding path for inspecting any forwarded packets that have been discarded at the forwarding layer. + +FWPM_LAYER_INBOUND_TRANSPORT_V4 / FWPM_LAYER_INBOUND_TRANSPORT_V6 + +This filtering layer is located in the receive path just after a received packet's transport header has been parsed by the network stack at the transport layer, but before any transport layer processing takes place. + +FWPM_LAYER_INBOUND_TRANSPORT_V4_DISCARD / FWPM_LAYER_INBOUND_TRANSPORT_V6_DISCARD + +This filtering layer is located in the receive path for inspecting any received packets that have been discarded at the transport layer. + +FWPM_LAYER_OUTBOUND_TRANSPORT_V4 / FWPM_LAYER_OUTBOUND_TRANSPORT_V6 + +This filtering layer is located in the send path just after a sent packet has been passed to the network layer for processing but before any network layer processing takes place. This filtering layer is located at the top of the network layer instead of at the bottom of the transport layer so that any packets that are sent by third-party transports or as raw packets are filtered at this layer. + +FWPM_LAYER_OUTBOUND_TRANSPORT_V4_DISCARD / FWPM_LAYER_OUTBOUND_TRANSPORT_V6_DISCARD + +This filtering layer is located in the send path for inspecting any sent packets that have been discarded at the transport layer. + +FWPM_LAYER_STREAM_V4 / FWPM_LAYER_STREAM_V6 + +This filtering layer is located in the stream data path. This layer allows for inspecting network data on a per stream basis. At the stream layer, the network data is bidirectional. + +FWPM_LAYER_STREAM_V4_DISCARD / FWPM_LAYER_STREAM_V6_DISCARD + +This filtering layer is located in the stream data path for inspecting any stream data that has been discarded. + +FWPM_LAYER_DATAGRAM_DATA_V4 / FWPM_LAYER_DATAGRAM_DATA_V6 + +This filtering layer is located in the datagram data path. This layer allows for inspecting network data on a per datagram basis. At the datagram layer, the network data is bidirectional. + +FWPM_LAYER_DATAGRAM_DATA_V4_DISCARD / FWPM_LAYER_DATAGRAM_DATA_V6_DISCARD + +This filtering layer is located in the datagram data path for inspecting any datagrams that have been discarded. + +FWPM_LAYER_INBOUND_ICMP_ERROR_V4 / FWPM_LAYER_INBOUND_ICMP_ERROR_V6 + +This filtering layer is located in the receive path for inspecting received ICMP error messages for the transport protocol. + +FWPM_LAYER_INBOUND_ICMP_ERROR_V4_DISCARD / FWPM_LAYER_INBOUND_ICMP_ERROR_V6_DISCARD + +This filtering layer is located in the receive path for inspecting received ICMP error messages that have been discarded. + +FWPM_LAYER_OUTBOUND_ICMP_ERROR_V4 / FWPM_LAYER_OUTBOUND_ICMP_ERROR_V6 + +This filtering layer is located in the send path for inspecting received ICMP error messages for the transport protocol. + +FWPM_LAYER_OUTBOUND_ICMP_ERROR_V4_DISCARD / FWPM_LAYER_OUTBOUND_ICMP_ERROR_V6_DISCARD + +This filtering layer is located in the send path for inspecting received ICMP error messages that have been discarded. + +FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V4 / FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V6 + +This filtering layer allows for authorizing transport port assignments, bind requests, promiscuous mode requests, and raw mode requests. + +See ALE Layers for more information. + +FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V4_DISCARD / FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V6_DISCARD + +This filtering layer allows for inspecting the following discarded items: transport port assignments, bind requests, promiscuous mode requests, and raw mode requests. + +FWPM_LAYER_ALE_AUTH_LISTEN_V4 / FWPM_LAYER_ALE_AUTH_LISTEN_V6 + +This filtering layer allows for authorizing TCP listen requests. + +See ALE Layers for more information. + +FWPM_LAYER_ALE_AUTH_LISTEN_V4_DISCARD / FWPM_LAYER_ALE_AUTH_LISTEN_V6_DISCARD + +This filtering layer allows for inspecting TCP listen requests that have been discarded. + +FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4 / FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6 + +This filtering layer allows for authorizing accept requests for incoming TCP connections, as well as authorizing incoming non-TCP traffic based on the first packet received. + +See ALE Layers for more information. + +FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4_DISCARD / FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6_DISCARD + +This filtering layer allows for inspecting accept requests for incoming TCP connections that have been discarded, as well as inspecting authorizations for incoming non-TCP traffic that have been discarded. + +FWPM_LAYER_ALE_AUTH_CONNECT_V4 / FWPM_LAYER_ALE_AUTH_CONNECT_V6 + +This filtering layer allows for authorizing connect requests for outgoing TCP connections, as well as authorizing outgoing non-TCP traffic based on the first packet sent. + +See ALE Layers for more information. + +FWPM_LAYER_ALE_AUTH_CONNECT_V4_DISCARD / FWPM_LAYER_ALE_AUTH_CONNECT_V6_DISCARD + +This filtering layer allows for inspecting connect requests for outgoing TCP connections that have been discarded, as well as inspecting authorizations for outgoing non-TCP traffic that have been discarded. + +FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4 / FWPM_LAYER_ALE_FLOW_ESTABLISHED_V6 + +This filtering layer allows for notification of when a TCP connection has been established, or when non-TCP traffic has been authorized. + +See ALE Layers for more information. + +FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4_DISCARD / FWPM_LAYER_ALE_FLOW_ESTABLISHED_V6_DISCARD + +This filtering layer allows for inspecting when an established TCP connection has been discarded at the flow established layer, as well as when authorized non-TCP traffic has been discarded at the flow established layer. + +FWPM_LAYER_INBOUND_MAC_FRAME_ETHERNET + +This filtering layer is located in the receive path after the MAC (802.3) layer processing has occurred but before the frame is processed by the framing layer. This is the layer after native in which all frames look like Ethernet frames. + + Note + +Available only in Windows 8 and Windows Server 2012. + +FWPM_LAYER_OUTBOUND_MAC_FRAME_ETHERNET + +This filtering layer is located in the send path after the framing layer processing has occurred but before the frame is processed by the MAC (802.3) layer. This is the layer after native in which all frames look like Ethernet frames. + + Note + +Available only in Windows 8 and Windows Server 2012. + +FWPM_LAYER_INBOUND_MAC_FRAME_NATIVE + +This filtering layer is located in the receive path after the MAC layer processing has occurred but before the frame is processed by the framing layer. It is the first layer after the Miniport delivers the frame to NDIS. + + Note + +Available only in Windows 8 and Windows Server 2012. + +FWPM_LAYER_OUTBOUND_MAC_FRAME_NATIVE + +This filtering layer is located in the send path after the framing layer processing has occurred but before the frame is processed by the MAC (Native 802.11) layer. It is the first layer after the Miniport delivers the frame to NDIS. + + Note + +Available only in Windows 8 and Windows Server 2012. + +FWPM_LAYER_INGRESS_VSWITCH_ETHERNET + +This filtering layer is located in the vSwitch ingress path just after the MAC header has been parsed, but before any MAC header processing takes place. + + Note + +Available only in Windows 8 and Windows Server 2012. + +FWPM_LAYER_EGRESS_VSWITCH_ETHERNET + +This filtering layer is located in the vSwitch egress path just after the MAC header has been parsed, but before any MAC header processing takes place. + + Note + +Available only in Windows 8 and Windows Server 2012. + +FWPM_LAYER_INGRESS_VSWITCH_TRANSPORT_V4 / FWPM_LAYER_INGRESS_VSWITCH_TRANSPORT_V6 + +This filtering layer is located in the vSwitch ingress path just after the MAC header has been parsed, but before any MAC header processing takes place. This layer allows for TRANSPORT level filtering conditions to aid in filtering traffic. + +If a vSwitchPort is in PVLAN or trunk mode, filters at this layer will be bypassed, letting the traffic flow through without any filtering. + +If IPv4 is uninstalled in the host, filters in this layer will cause packets to be dropped. + + Note + +Available only in Windows 8 and Windows Server 2012. + +FWPM_LAYER_EGRESS_VSWITCH_TRANSPORT_V4 / FWPM_LAYER_EGRESS_VSWITCH_TRANSPORT_V6 + +This filtering layer is located in the vSwitch egress path just after the MAC header has been parsed, but before any MAC header processing takes place. This layer allows for TRANSPORT level filtering conditions to aid in filtering traffic. + +If a vSwitchPort is in PVLAN or trunk mode, filters at this layer will be bypassed, letting the traffic flow through without any filtering. + +If IPv4 is uninstalled in the host, filters in this layer will cause packets to be dropped. + + Note + +Available only in Windows 8 and Windows Server 2012. + +FWPM_LAYER_IPSEC_KM_DEMUX_V4 / FWPM_LAYER_IPSEC_KM_DEMUX_V6 + +This filtering layer is used to determine which keying modules are invoked when the local system is the initiator. This is a user-mode filtering layer. + +FWPM_LAYER_IPSEC_V4 / FWPM_LAYER_IPSEC_V6 + +This filtering layer allows the keying module to look up quick-mode policy information when negotiating quick-mode security associations. This is a user-mode filtering layer. + +FWPM_LAYER_IKEEXT_V4 / FWPM_LAYER_IKEEXT_V6 + +This filtering layer allows the IKE and authenticated IP modules to look up main-mode policy information when negotiating main-mode security associations. This is a user-mode filtering layer. + +FWPM_LAYER_RPC_UM + +This filtering layer allows for inspecting the RPC data fields that are available in user mode. This is a user-mode filtering layer. + +FWPM_LAYER_RPC_EPMAP + +This filtering layer allows for inspecting the RPC data fields that are available in user mode during endpoint resolution. This is a user-mode filtering layer. + +FWPM_LAYER_RPC_EP_ADD + +This filtering layer allows for inspecting the RPC data fields that are available in user mode when a new endpoint is added. This is a user-mode filtering layer. + +FWPM_LAYER_RPC_PROXY_CONN + +This filtering layer allows for inspecting the RpcProxy connection requests. This is a user-mode filtering layer. + +FWPM_LAYER_RPC_PROXY_IF + +This filtering layer allows for inspecting the interface used for RpcProxy connections. This is a user-mode filtering layer. + +FWPM_LAYER_KM_AUTHORIZATION + +This filtering layer allows for authorizing security association establishment. + +See ALE Layers for more information. + +FWPM_LAYER_NAME_RESOLUTION_CACHE_V4 / FWPM_LAYER_NAME_RESOLUTION_CACHE_V6 + +This filtering layer allows for querying the names recently resolved by the system. + +See ALE Layers for more information. + +FWPM_LAYER_ALE_RESOURCE_RELEASE_V4 / FWPM_LAYER_ALE_RESOURCE_RELEASE_V6 + +This filtering layer allows for resources previously allocated to be released. + +See ALE Layers for more information. + +FWPM_LAYER_ALE_ENDPOINT_CLOSURE_V4 / FWPM_LAYER_ALE_ENDPOINT_CLOSURE_V6 + +This filtering layer allows for tracking de-activation of connected TCP flow or UDP sockets. + +See ALE Layers for more information. + +FWPM_LAYER_ALE_CONNECT_REDIRECT_V4 / FWPM_LAYER_ALE_CONNECT_REDIRECT_V6 + +This filtering layer allows for modification of addresses and ports at the bind_redirect layer. + +For TCP, the local address and/or local port as well as the remote address and/or remote port can be modified. + +For UDP, the local address and/or local port can be modified. + +See ALE Layers for more information. + +FWPM_LAYER_ALE_BIND_REDIRECT_V4 / FWPM_LAYER_ALE_BIND_REDIRECT_V6 + +This filtering layer allows for modification of bind() and connect() calls and selection of host routes. + +See ALE Layers for more information. + +FWPM_LAYER_STREAM_PACKET_V4 / FWPM_LAYER_STREAM_PACKET_V6 + +This filtering layer allows for inspection of TCP packets including handshake and flow control exchanges. + +See ALE Layers for more information. + +Remarks +These filtering layer identifiers are also referred to as management filtering layer identifiers. WFP API also contains a set of run-time filtering layer identifiers, documented in the Windows Driver Kit (WDK). Run-time filtering layer identifiers are LUIDs, and therefore are smaller, only 64 bits in size, compared to the management filtering layer identifiers, which are 128 bits in size. + +Management filtering layer identifiers and run-time filtering layer identifiers point to the same layers. + +Management filtering layer identifiers are used by functions that interact with the Base Filtering Engine (BFE) from either user mode or kernel mode (for example, FwpmFilterAdd0). + +Run-time filtering layer identifiers are used by functions that interact with the filter engine from kernel mode only (for example, FwpsFlowAssociateContext0, FwpsStreamInjectAsync0), and in data structures that come directly from the kernel (for example, FWPM_NET_EVENT_CLASSIFY_DROP0). +``` + +`Windows Driver Development/Windows NT File System Internals/README.txt`: + +```txt +README.TXT + +The sources for the examples and a working file system filter driver are +compressed and contained in the file examples.zip. Unzipping the file +produces three folders: + +1. filesys, containing a README file and the sources for the example file + system driver +2. fsrec, containing a README file and the sources for the example FSD + recognizer +3. filter-high, containing a README file and the sources for the example + file system filter driver + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/WS_FTP.LOG`: + +```LOG +97.08.05 15:11 B C:\Users\Erik\NTFdisk\filesys\readme1.txt --> geode /home/eray readme1.txt +97.08.06 14:20 B C:\Users\erik\NTFdisk\filesys\readme1.txt <-- geode /work/nt/file.internals/disk readme1.txt + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/event/MAKE-MC.BAT`: + +```BAT +@echo off +REM The following explain the options used : +REM v = verbose +REM c = set Customer bit in all message ids. +REM s = insert symbolic name as first line of each message. +REM h = pathname where (created) header file should be stored. +REM U = output file should be in Unicode. + +\mstools-351\bin\mc -vcs -h ..\inc -U errmsg.msg + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/event/MAKEFILE`: + +``` +########################################################################## +# +# DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source +# file to this component. This file merely indirects to the real make file +# that is shared by all the components of NT. +# +########################################################################## +!INCLUDE $(NTMAKEENV)\makefile.def + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/event/README.TXT`: + +```TXT +Use the MAKE-MC.BAT file to compile the errmsg.msg file using mc.exe, +which is the Microsoft message compiler. Then use build to create the +required dll. + + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/event/SOURCES`: + +``` +# - Execute the "build" command to make the Sample FSD Event-related dll. + +# +# The TARGETNAME variable is defined by the developer. It is the name of +# the target (component) that is being built by this makefile. It +# should NOT include any path or file extension information. +# + +TARGETNAME=sfsdevnt + +TARGETPATH=obj + +TARGETTYPE=DYNLINK + +INCLUDES=..\inc + +# Source files common to multiple platforms + +SOURCES=dummy.c \ + errmsg.rc + +UNICODE=1 + +# +# Next specify any additional options for the compiler. +# + +C_DEFINES=-DUNICODE + +# +# Next specify options for the linker. +# + +DLLBASE=0x62900000 + +UMRES=obj\*\sfsdevnt.res + + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/event/dummy.c`: + +```c +void +DummyEntryPoint(void) +{ +} + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/event/errmsg.msg`: + +```msg +;/************************************************************************* +;* +;* File: errmsg.msg +;* +;* Product: Sample FSD +;* +;* Module: Sample FSD Event Log Messages +;* +;* Description: +;* Contains error strings in a format understandable to the message compiler. +;* Please compile (using mc) with the -c option which will set the +;* "Customer" bit in all errors. +;* Use values beginning at 0xA000 (e.g. 0xA001) for the Sample FSD +;* errors. +;* Do NOT use %1 for insertion strings. The I/O manager assumes that +;* the first insertion string is the name of the driver/device. +;* +;* (c) 1996-97 Rajeev Nagar, All Rights Reserved +;* +;*************************************************************************/ +MessageIdTypedef=ULONG + +SeverityNames=(Success=0x0:STATUS_SEVERITY_SUCCESS + Informational=0x1:STATUS_SEVERITY_INFORMATIONAL + Warning=0x2:STATUS_SEVERITY_WARNING + Error=0x3:STATUS_SEVERITY_ERROR + ) + +FacilityNames=(IO=0x004) + +MessageId=0xA001 Facility=IO Severity=Error +SymbolicName=SFSD_ERROR_INTERNAL_ERROR +Language=English +The Sample FSD encountered an internal error. Please check log data information. +. + + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/event/errmsg.rc`: + +```rc +LANGUAGE 0x9,0x1 +1 11 MSG00001.bin + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/event/sfsdevnt.def`: + +```def +LIBRARY SFSDEVENT + +DESCRIPTION 'Sample FSD Event Log Messages' + +EXPORTS + + DummyEntryPoint + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/inc/errmsg.h`: + +```h +/************************************************************************* +* +* File: errmsg.msg +* +* Product: Sample FSD +* +* Module: Sample FSD Event Log Messages +* +* Description: +* Contains error strings in a format understandable to the message compiler. +* Please compile (using mc) with the -c option which will set the +* "Customer" bit in all errors. +* Use values beginning at 0xA000 (e.g. 0xA001) for the Sample FSD +* errors. +* Do NOT use %1 for insertion strings. The I/O manager assumes that +* the first insertion string is the name of the driver/device. +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ +// +// Values are 32 bit values layed out as follows: +// +// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 +// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +// +---+-+-+-----------------------+-------------------------------+ +// |Sev|C|R| Facility | Code | +// +---+-+-+-----------------------+-------------------------------+ +// +// where +// +// Sev - is the severity code +// +// 00 - Success +// 01 - Informational +// 10 - Warning +// 11 - Error +// +// C - is the Customer code flag +// +// R - is a reserved bit +// +// Facility - is the facility code +// +// Code - is the facility's status code +// +// +// Define the facility codes +// + + +// +// Define the severity codes +// +#define STATUS_SEVERITY_WARNING 0x2 +#define STATUS_SEVERITY_SUCCESS 0x0 +#define STATUS_SEVERITY_INFORMATIONAL 0x1 +#define STATUS_SEVERITY_ERROR 0x3 + + +// +// MessageId: SFSD_ERROR_INTERNAL_ERROR +// +// MessageText: +// +// The Sample FSD encountered an internal error. Please check log data information. +// +#define SFSD_ERROR_INTERNAL_ERROR ((ULONG)0xE004A001L) + + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/inc/protos.h`: + +```h +/************************************************************************* +* +* File: protos.h +* +* Module: Sample File System Driver (Kernel mode execution only) +* +* Description: +* Contains the prototypes for functions in this sample FSD. +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +#ifndef _SFSD_PROTOS_H_ +#define _SFSD_PROTOS_H_ + +/************************************************************************* +* Prototypes for the file sfsdinit.c +*************************************************************************/ +extern NTSTATUS DriverEntry( +PDRIVER_OBJECT DriverObject, // created by the I/O sub-system +PUNICODE_STRING RegistryPath); // path to the registry key + +extern void SFsdInitializeFunctionPointers( +PDRIVER_OBJECT DriverObject); // created by the I/O sub-system + +/************************************************************************* +* Prototypes for the file create.c +*************************************************************************/ +extern NTSTATUS SFsdCreate( +PDEVICE_OBJECT DeviceObject, // the logical volume device object +PIRP Irp); // I/O Request Packet + +extern NTSTATUS SFsdCommonCreate( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp); + +extern NTSTATUS SFsdOpenVolume( +PtrSFsdVCB PtrVCB, // volume to be opened +PtrSFsdIrpContext PtrIrpContext, // IRP context +PIRP PtrIrp, // original/user IRP +unsigned short ShareAccess, // share access +PIO_SECURITY_CONTEXT PtrSecurityContext, // caller's context (incl access) +PFILE_OBJECT PtrNewFileObject); // I/O Mgr. created file object + +extern void SFsdInitializeFCB( +PtrSFsdFCB PtrNewFCB, // FCB structure to be initialized +PtrSFsdVCB PtrVCB, // logical volume (VCB) pointer +PtrSFsdObjectName PtrObjectName, // name of the object +uint32 Flags, // is this a file/directory, etc. +PFILE_OBJECT PtrFileObject);// optional file object to be initialized + +/************************************************************************* +* Prototypes for the file misc.c +*************************************************************************/ +extern NTSTATUS SFsdInitializeZones( +void); + +extern void SFsdDestroyZones( +void); + +extern BOOLEAN SFsdIsIrpTopLevel( +PIRP Irp); // the IRP sent to our dispatch routine + +extern long SFsdExceptionFilter( +PtrSFsdIrpContext PtrIrpContext, +PEXCEPTION_POINTERS PtrExceptionPointers); + +extern NTSTATUS SFsdExceptionHandler( +PtrSFsdIrpContext PtrIrpContext, +PIRP Irp); + +extern void SFsdLogEvent( +NTSTATUS SFsdEventLogId, // the SFsd private message id +NTSTATUS RC); // any NT error code we wish to log ... + +extern PtrSFsdObjectName SFsdAllocateObjectName( +void); + +extern void SFsdReleaseObjectName( +PtrSFsdObjectName PtrObjectName); + +extern PtrSFsdCCB SFsdAllocateCCB( +void); + +extern void SFsdReleaseCCB( +PtrSFsdCCB PtrCCB); + +extern PtrSFsdFCB SFsdAllocateFCB( +void); + +extern NTSTATUS SFsdCreateNewFCB( +PtrSFsdFCB *ReturnedFCB, +PLARGE_INTEGER AllocationSize, +PLARGE_INTEGER EndOfFile, +PFILE_OBJECT PtrFileObject, +PtrSFsdVCB PtrVCB); + +extern void SFsdReleaseFCB( +PtrSFsdFCB PtrFCB); + +extern PtrSFsdFileLockInfo SFsdAllocateByteLocks( +void); + +extern void SFsdReleaseByteLocks( +PtrSFsdFileLockInfo PtrByteLocks); + +extern PtrSFsdIrpContext SFsdAllocateIrpContext( +PIRP Irp, +PDEVICE_OBJECT PtrTargetDeviceObject); + +extern void SFsdReleaseIrpContext( +PtrSFsdIrpContext PtrIrpContext); + +extern NTSTATUS SFsdPostRequest( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp); + +extern void SFsdCommonDispatch( +void *Context); // actually an IRPContext structure + +extern void SFsdInitializeVCB( +PDEVICE_OBJECT PtrVolumeDeviceObject, +PDEVICE_OBJECT PtrTargetDeviceObject, +PVPB PtrVPB); + +/************************************************************************* +* Prototypes for the file cleanup.c +*************************************************************************/ +extern NTSTATUS SFsdCleanup( +PDEVICE_OBJECT DeviceObject, // the logical volume device object +PIRP Irp); // I/O Request Packet + +extern NTSTATUS SFsdCommonCleanup( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp); + +/************************************************************************* +* Prototypes for the file close.c +*************************************************************************/ +extern NTSTATUS SFsdClose( +PDEVICE_OBJECT DeviceObject, // the logical volume device object +PIRP Irp); // I/O Request Packet + +extern NTSTATUS SFsdCommonClose( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp); + +/************************************************************************* +* Prototypes for the file read.c +*************************************************************************/ +extern NTSTATUS SFsdRead( +PDEVICE_OBJECT DeviceObject, // the logical volume device object +PIRP Irp); // I/O Request Packet + +extern NTSTATUS SFsdCommonRead( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp); + +extern void *SFsdGetCallersBuffer( +PIRP PtrIrp); + +extern NTSTATUS SFsdLockCallersBuffer( +PIRP PtrIrp, +BOOLEAN IsReadOperation, +uint32 Length); + +extern void SFsdMdlComplete( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp, +PIO_STACK_LOCATION PtrIoStackLocation, +BOOLEAN ReadCompletion); + +/************************************************************************* +* Prototypes for the file write.c +*************************************************************************/ +extern NTSTATUS SFsdWrite( +PDEVICE_OBJECT DeviceObject, // the logical volume device object +PIRP Irp); // I/O Request Packet + +extern NTSTATUS SFsdCommonWrite( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp); + +extern void SFsdDeferredWriteCallBack ( +void *Context1, // Should be PtrIrpContext +void *Context2); // Should be PtrIrp + +/************************************************************************* +* Prototypes for the file fileinfo.c +*************************************************************************/ +extern NTSTATUS SFsdFileInfo( +PDEVICE_OBJECT DeviceObject, // the logical volume device object +PIRP Irp); // I/O Request Packet + +extern NTSTATUS SFsdCommonFileInfo( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp); + +extern NTSTATUS SFsdGetBasicInformation( +PtrSFsdFCB PtrFCB, +PFILE_BASIC_INFORMATION PtrBuffer, +long *PtrReturnedLength); + +extern NTSTATUS SFsdSetBasicInformation( +PtrSFsdFCB PtrFCB, +PtrSFsdCCB PtrCCB, +PFILE_OBJECT PtrFileObject, +PFILE_BASIC_INFORMATION PtrBuffer); + +extern NTSTATUS SFsdSetDispositionInformation( +PtrSFsdFCB PtrFCB, +PtrSFsdCCB PtrCCB, +PtrSFsdVCB PtrVCB, +PFILE_OBJECT PtrFileObject, +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp, +PFILE_DISPOSITION_INFORMATION PtrBuffer); + +extern NTSTATUS SFsdSetAllocationInformation( +PtrSFsdFCB PtrFCB, +PtrSFsdCCB PtrCCB, +PtrSFsdVCB PtrVCB, +PFILE_OBJECT PtrFileObject, +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp, +PFILE_ALLOCATION_INFORMATION PtrBuffer); + +/************************************************************************* +* Prototypes for the file flush.c +*************************************************************************/ +extern NTSTATUS SFsdFlush( +PDEVICE_OBJECT DeviceObject, // the logical volume device object +PIRP Irp); // I/O Request Packet + +extern NTSTATUS SFsdCommonFlush( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp); + +extern void SFsdFlushAFile( +PtrSFsdNTRequiredFCB PtrReqdFCB, +PIO_STATUS_BLOCK PtrIoStatus); + +extern void SFsdFlushLogicalVolume( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp, +PtrSFsdVCB PtrVCB); + +extern NTSTATUS SFsdFlushCompletion( +PDEVICE_OBJECT PtrDeviceObject, +PIRP PtrIrp, +PVOID Context); + +/************************************************************************* +* Prototypes for the file dircntrl.c +*************************************************************************/ +extern NTSTATUS SFsdDirControl( +PDEVICE_OBJECT DeviceObject, // the logical volume device object +PIRP Irp); // I/O Request Packet + +extern NTSTATUS SFsdCommonDirControl( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp); + +extern NTSTATUS SFsdQueryDirectory( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp, +PIO_STACK_LOCATION PtrIoStackLocation, +PFILE_OBJECT PtrFileObject, +PtrSFsdFCB PtrFCB, +PtrSFsdCCB PtrCCB); + +extern NTSTATUS SFsdNotifyChangeDirectory( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp, +PIO_STACK_LOCATION PtrIoStackLocation, +PFILE_OBJECT PtrFileObject, +PtrSFsdFCB PtrFCB, +PtrSFsdCCB PtrCCB); + +/************************************************************************* +* Prototypes for the file devcntrl.c +*************************************************************************/ +extern NTSTATUS SFsdDeviceControl( +PDEVICE_OBJECT DeviceObject, // the logical volume device object +PIRP Irp); // I/O Request Packet + +extern NTSTATUS SFsdCommonDeviceControl( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp); + +extern NTSTATUS SFsdDevIoctlCompletion( +PDEVICE_OBJECT PtrDeviceObject, +PIRP PtrIrp, +void *Context); + +extern NTSTATUS SFsdHandleQueryPath( +void *BufferPointer); + +/************************************************************************* +* Prototypes for the file shutdown.c +*************************************************************************/ +extern NTSTATUS SFsdShutdown( +PDEVICE_OBJECT DeviceObject, // the logical volume device object +PIRP Irp); // I/O Request Packet + +extern NTSTATUS SFsdCommonShutdown( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp); + +/************************************************************************* +* Prototypes for the file fastio.c +*************************************************************************/ +extern BOOLEAN SFsdFastIoCheckIfPossible( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN ULONG Length, +IN BOOLEAN Wait, +IN ULONG LockKey, +IN BOOLEAN CheckForReadOperation, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFsdFastIoRead( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN ULONG Length, +IN BOOLEAN Wait, +IN ULONG LockKey, +OUT PVOID Buffer, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFsdFastIoWrite( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN ULONG Length, +IN BOOLEAN Wait, +IN ULONG LockKey, +OUT PVOID Buffer, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFsdFastIoQueryBasicInfo( +IN PFILE_OBJECT FileObject, +IN BOOLEAN Wait, +OUT PFILE_BASIC_INFORMATION Buffer, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFsdFastIoQueryStdInfo( +IN PFILE_OBJECT FileObject, +IN BOOLEAN Wait, +OUT PFILE_STANDARD_INFORMATION Buffer, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFsdFastIoLock( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN PLARGE_INTEGER Length, +PEPROCESS ProcessId, +ULONG Key, +BOOLEAN FailImmediately, +BOOLEAN ExclusiveLock, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFsdFastIoUnlockSingle( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN PLARGE_INTEGER Length, +PEPROCESS ProcessId, +ULONG Key, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFsdFastIoUnlockAll( +IN PFILE_OBJECT FileObject, +PEPROCESS ProcessId, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFsdFastIoUnlockAllByKey( +IN PFILE_OBJECT FileObject, +PEPROCESS ProcessId, +ULONG Key, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject); + +extern void SFsdFastIoAcqCreateSec( +IN PFILE_OBJECT FileObject); + +extern void SFsdFastIoRelCreateSec( +IN PFILE_OBJECT FileObject); + +extern BOOLEAN SFsdAcqLazyWrite( +IN PVOID Context, +IN BOOLEAN Wait); + +extern void SFsdRelLazyWrite( +IN PVOID Context); + +extern BOOLEAN SFsdAcqReadAhead( +IN PVOID Context, +IN BOOLEAN Wait); + +extern void SFsdRelReadAhead( +IN PVOID Context); + +// the remaining are only valid under NT Version 4.0 and later +#if(_WIN32_WINNT >= 0x0400) + +extern BOOLEAN SFsdFastIoQueryNetInfo( +IN PFILE_OBJECT FileObject, +IN BOOLEAN Wait, +OUT PFILE_NETWORK_OPEN_INFORMATION Buffer, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFsdFastIoMdlRead( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN ULONG Length, +IN ULONG LockKey, +OUT PMDL *MdlChain, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFsdFastIoMdlReadComplete( +IN PFILE_OBJECT FileObject, +OUT PMDL MdlChain, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFsdFastIoPrepareMdlWrite( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN ULONG Length, +IN ULONG LockKey, +OUT PMDL *MdlChain, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFsdFastIoMdlWriteComplete( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +OUT PMDL MdlChain, +IN PDEVICE_OBJECT DeviceObject); + +extern NTSTATUS SFsdFastIoAcqModWrite( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER EndingOffset, +OUT PERESOURCE *ResourceToRelease, +IN PDEVICE_OBJECT DeviceObject); + +extern NTSTATUS SFsdFastIoRelModWrite( +IN PFILE_OBJECT FileObject, +IN PERESOURCE ResourceToRelease, +IN PDEVICE_OBJECT DeviceObject); + +extern NTSTATUS SFsdFastIoAcqCcFlush( +IN PFILE_OBJECT FileObject, +IN PDEVICE_OBJECT DeviceObject); + +extern NTSTATUS SFsdFastIoRelCcFlush( +IN PFILE_OBJECT FileObject, +IN PDEVICE_OBJECT DeviceObject); + +#endif // (_WIN32_WINNT >= 0x0400) + +#endif // _SFSD_PROTOS_H_ + + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/inc/sfsd.h`: + +```h +/************************************************************************* +* +* File: sfsd.h +* +* Module: Sample File System Driver (Kernel mode execution only) +* +* Description: +* The main include file for the sample file system driver. +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +#ifndef _SFSD_SFSD_H_ +#define _SFSD_SFSD_H_ + +// some constant definitions +#define SFSD_PANIC_IDENTIFIER (0x86427531) + +// any directory information SFSD obtains from the local file system +// will use a buffer of the following size ... (in KB) +#define SFSD_READ_DIR_BUFFER_LENGTH (512) + +// Common include files - should be in the include dir of the MS supplied IFS Kit +#include + +// the following include files should be in the inc sub-dir associated with this driver +#include "struct.h" +#include "protos.h" +#include "errmsg.h" + +// global variables - minimize these +extern SFsdData SFsdGlobalData; + +// try-finally simulation +#define try_return(S) { S; goto try_exit; } +#define try_return1(S) { S; goto try_exit1; } +#define try_return2(S) { S; goto try_exit2; } + +// some global (helpful) macros +#define SFsdSetFlag(Flag, Value) ((Flag) |= (Value)) +#define SFsdClearFlag(Flag, Value) ((Flag) &= ~(Value)) + +#define SFsdQuadAlign(Value) ((((uint32)(Value)) + 7) & 0xfffffff8) + +// to perform a bug-check (panic), the following macro is used +#define SFsdPanic(arg1, arg2, arg3) \ + (KeBugCheckEx(SFSD_PANIC_IDENTIFIER, SFSD_BUG_CHECK_ID | __LINE__, (uint32)(arg1), (uint32)(arg2), (uint32)(arg3))) + +// a convenient macro (must be invoked in the context of the thread that acquired the resource) +#define SFsdReleaseResource(Resource) \ + (ExReleaseResourceForThreadLite((Resource), ExGetCurrentResourceThread())) + +// each file has a unique bug-check identifier associated with it. +// Here is a list of constant definitions for these identifiers +#define SFSD_FILE_INIT (0x00000001) +#define SFSD_FILE_REGISTRY (0x00000002) +#define SFSD_FILE_CREATE (0x00000003) +#define SFSD_FILE_CLEANUP (0x00000004) +#define SFSD_FILE_CLOSE (0x00000005) +#define SFSD_FILE_READ (0x00000006) +#define SFSD_FILE_WRITE (0x00000007) +#define SFSD_FILE_INFORMATION (0x00000008) +#define SFSD_FILE_FLUSH (0x00000009) +#define SFSD_FILE_VOL_INFORMATION (0x0000000A) +#define SFSD_FILE_DIR_CONTROL (0x0000000B) +#define SFSD_FILE_FILE_CONTROL (0x0000000C) +#define SFSD_FILE_DEVICE_CONTROL (0x0000000D) +#define SFSD_FILE_SHUTDOWN (0x0000000E) +#define SFSD_FILE_LOCK_CONTROL (0x0000000F) +#define SFSD_FILE_SECURITY (0x00000010) +#define SFSD_FILE_EXT_ATTR (0x00000011) +#define SFSD_FILE_MISC (0x00000012) +#define SFSD_FILE_FAST_IO (0x00000013) + +#if DBG +#define SFsdBreakPoint() DbgBreakPoint() +#else +#define SFsdBreakPoint() +#endif + +#endif // _SFSD_SFSD_H_ + + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/inc/struct.h`: + +```h +/************************************************************************* +* +* File: struct.h +* +* Module: Sample File System Driver (Kernel mode execution only) +* +* Description: +* This file contains structure definitions for the sample file system +* driver. Note that all structures are prefixed with the letters +* "SFsd". The structures are all aligned using normal alignment +* used by the compiler (typically quad-word aligned). +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +#ifndef _SFSD_STRUCTURES_H_ +#define _SFSD_STRUCTURES_H_ + +/************************************************************************** + some useful definitions +**************************************************************************/ +#ifdef _CPU_X86_ +typedef char int8; +typedef short int16; +typedef int int32; + +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint32; + +// we will use the LARGE_INTEGER structure as defined by NT + +#else // Please define appropriate types here + +!!!! You must define your types here for compilation to proceed !!!! + +#endif // if _CPU_X86_ + +/************************************************************************** + some empty typedefs defined here so we can reference them easily +**************************************************************************/ +struct _SFsdIdentifier; +struct _SFsdObjectName; +struct _SFsdContextControlBlock; +struct _SFsdNTRequiredFCB; +struct _SFsdDiskDependentFCB; +struct _SFsdFileControlBlock; +struct _SFsdFileByteLocks; +struct _SFsdVolumeControlBlock; +struct _SFsdIrpContext; +struct _SFsdData; + +/************************************************************************** + each structure has a unique "node type" or signature associated with it +**************************************************************************/ +#define SFSD_NODE_TYPE_OBJECT_NAME (0xfdecba01) +#define SFSD_NODE_TYPE_CCB (0xfdecba02) +#define SFSD_NODE_TYPE_FCB (0xfdecba03) +#define SFSD_NODE_TYPE_LOCKS (0xfdecba04) +#define SFSD_NODE_TYPE_VCB (0xfdecba05) +#define SFSD_NODE_TYPE_IRP_CONTEXT (0xfdecba06) +#define SFSD_NODE_TYPE_GLOBAL_DATA (0xfdecba07) + +/************************************************************************** + every structure has a node type, and a node size associated with it. + The node type serves as a signature field. The size is used for + consistency checking ... +**************************************************************************/ +typedef struct _SFsdIdentifier { + uint32 NodeType; // a 32 bit identifier for the structure + uint32 NodeSize; // computed as sizeof(structure) +} SFsdIdentifier, *PtrSFsdIdentifier; + +/************************************************************************** + Structures for byte-range lock support. +**************************************************************************/ +typedef struct SFsdFileLockAnchor { + LIST_ENTRY GrantedFileLockList; + LIST_ENTRY PendingFileLockList; +} SFsdFileLockAnchor, *PtrSFsdFileLockAnchor; + +typedef struct SFsdFileLockInfo { + SFsdIdentifier NodeIdentifier; + uint32 FileLockFlags; + PVOID OwningProcess; + LARGE_INTEGER StartingOffset; + LARGE_INTEGER Length; + LARGE_INTEGER EndingOffset; + ULONG Key; + BOOLEAN ExclusiveLock; + PIRP PendingIRP; + LIST_ENTRY NextFileLockEntry; +} SFsdFileLockInfo, *PtrSFsdFileLockInfo; + +#define SFSD_BYTE_LOCK_NOT_FROM_ZONE (0x80000000) +#define SFSD_BYTE_LOCK_IS_PENDING (0x00000001) + +/************************************************************************** + Every open on-disk object must have a name associated with it + This name has two components: + (a) the path-name (prefix) that leads to this on-disk object + (b) the name of the object itself + Note that with multiply linked objects, a single object might be + associated with more than one name structure. + This sample FSD does not correctly support multiply linked objects. + + This structure must be quad-word aligned because it is zone allocated. +**************************************************************************/ +typedef struct _SFsdObjectName { + SFsdIdentifier NodeIdentifier; + uint32 ObjectNameFlags; + // an absolute pathname of the object is stored below + UNICODE_STRING ObjectName; +} SFsdObjectName, *PtrSFsdObjectName; + +#define SFSD_OB_NAME_NOT_FROM_ZONE (0x80000000) + +/************************************************************************** + Each file open instance is represented by a context control block. + For each successful create/open request; a file object and a CCB will + be created. + For open operations performed internally by the FSD, there may not + exist file objects; but a CCB will definitely be created. + + This structure must be quad-word aligned because it is zone allocated. +**************************************************************************/ +typedef struct _SFsdContextControlBlock { + SFsdIdentifier NodeIdentifier; + // ptr to the associated FCB + struct _SFsdFileControlBlock *PtrFCB; + // all CCB structures for a FCB are linked together + LIST_ENTRY NextCCB; + // each CCB is associated with a file object + PFILE_OBJECT PtrFileObject; + // flags (see below) associated with this CCB + uint32 CCBFlags; + // current byte offset is required sometimes + LARGE_INTEGER CurrentByteOffset; + // if this CCB represents a directory object open, we may + // need to maintain a search pattern + PSTRING DirectorySearchPattern; + // we must maintain user specified file time values + uint32 UserSpecifiedTime; +} SFsdCCB, *PtrSFsdCCB; + + +/************************************************************************** + the following CCBFlags values are relevant. These flag + values are bit fields; therefore we can test whether + a bit position is set (1) or not set (0). +**************************************************************************/ + +// some on-disk file/directories are opened by SFSD itself +// as opposed to being opened on behalf of a user process +#define SFSD_CCB_OPENED_BY_SFSD (0x00000001) +// the file object specified synchronous access at create/open time. +// this implies that SFSD must maintain the current byte offset +#define SFSD_CCB_OPENED_FOR_SYNC_ACCESS (0x00000002) +// file object specified sequential access for this file +#define SFSD_CCB_OPENED_FOR_SEQ_ACCESS (0x00000004) +// the CCB has had an IRP_MJ_CLEANUP issued on it. we must +// no longer allow the file object / CCB to be used in I/O requests. +#define SFSD_CCB_CLEANED (0x00000008) +// if we were invoked via the fast i/o path to perform file i/o; +// we should set the CCB access/modification time at cleanup +#define SFSD_CCB_ACCESSED (0x00000010) +#define SFSD_CCB_MODIFIED (0x00000020) +// if an application process set the file date time, we must +// honor that request and *not* overwrite the values at cleanup +#define SFSD_CCB_ACCESS_TIME_SET (0x00000040) +#define SFSD_CCB_MODIFY_TIME_SET (0x00000080) +#define SFSD_CCB_CREATE_TIME_SET (0x00000100) + +#define SFSD_CCB_NOT_FROM_ZONE (0x80000000) + +// this CCB was allocated for a "volume open" operation +#define SFSD_CCB_VOLUME_OPEN (0x00000100) + +/************************************************************************** + each open file/directory/volume is represented by a file control block. + NOTE: Currently, SFSD does not handle multiply linked files correctly. + In your FSD implementation, you must be careful about handling + such on-disk files correctly i.e. a single (unique) FCB must + represent an on-disk file/directory regardless of the path used + to access the on-disk object. + With the current SFSD implementation, an on-disk file object + with more than a single (hard) link will be treated incorrectly! + + Each FCB can logically be divided into two: + (a) a structure that must have a field of type FSRTL_COMMON_FCB_HEADER + as the first field in the structure. + This portion should also contain other structures/resources required + by the NT Cache Manager + We will call this structure the "NT Required" FCB. Note that this + portion of the FCB must be allocated from non-paged pool. + (b) the remainder of the FCB is dependent upon the particular FSD + requirements. + This portion of the FCB could possibly be allocated from paged + memory, though in the sample FSD, it will always be allocated + from non-paged pool. + + FCB structures are protected by the MainResource as well as the + PagingIoResource. Of course, if your FSD implementation requires + it, you can associate other syncronization structures with the + FCB. + + This structure must be quad-word aligned because it is zone allocated. +**************************************************************************/ +typedef struct _SFsdNTRequiredFCB { + // see Chapters 6-8 for an explanation of the fields here + FSRTL_COMMON_FCB_HEADER CommonFCBHeader; + SECTION_OBJECT_POINTERS SectionObject; + ERESOURCE MainResource; + ERESOURCE PagingIoResource; +} SFsdNTRequiredFCB, *PtrSFsdNTRequiredFCB; + +typedef struct _SFsdDiskDependentFCB { + // although the sample FSD does not maintain on-disk data structures, + // this structure serves as a reminder of the logical separation that + // your FSD can maintain between the disk dependent and the disk + // independent portions of the FCB. + uint16 DummyField; // placeholder +} SFsdDiskDependentFCB, *PtrSFsdDiskDependentFCB; + +typedef struct _SFsdFileControlBlock { + SFsdIdentifier NodeIdentifier; + // we will go ahead and embed the "NT Required FCB" right here. + // Note though that it is just as acceptable to simply allocate + // memory separately for the other half of the FCB and store a + // pointer to the "NT Required" portion here instead of embedding + // it ... + SFsdNTRequiredFCB NTRequiredFCB; + // the disk dependent portion of the FCB is embedded right here + SFsdDiskDependentFCB DiskDependentFCB; + // this FCB belongs to some mounted logical volume + struct _SFsdVolumeControlBlock *PtrVCB; + // to be able to access all open file(s) for a volume, we will + // link all FCB structures for a logical volume together + LIST_ENTRY NextFCB; + // some state information for the FCB is maintained using the + // flags field + uint32 FCBFlags; + // all CCB's for this particular FCB are linked off the following + // list head. + LIST_ENTRY NextCCB; + // NT requires that a file system maintain and honor the various + // SHARE_ACCESS modes ... + SHARE_ACCESS FCBShareAccess; + // to identify the lazy writer thread(s) we will grab and store + // the thread id here when a request to acquire resource(s) + // arrives .. + uint32 LazyWriterThreadID; + // whenever a file stream has a create/open operation performed, + // the Reference count below is incremented AND the OpenHandle count + // below is also incremented. + // When an IRP_MJ_CLEANUP is received, the OpenHandle count below + // is decremented. + // When an IRP_MJ_CLOSE is received, the Reference count below is + // decremented. + // When the Reference count goes down to zero, the FCB can be de-allocated. + // Note that a zero Reference count implies a zero OpenHandle count. + // This invariant must always hold true ... (if it is really an invariant, + // shoudn't the previous statement be redundant ... hmmm!!!) + uint32 ReferenceCount; + uint32 OpenHandleCount; + // for the sample fsd, there exists a 1-1 correspondence between an + // object name structure and a FCB + PtrSFsdObjectName FCBName; + // we will maintain some time information here to make our life easier + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + // Byte-range file lock support (we roll our own) + SFsdFileLockAnchor FCBByteRangeLock; + // The OPLOCK support package requires the following structure + OPLOCK FCBOplock; +} SFsdFCB, *PtrSFsdFCB; + +/************************************************************************** + the following FCBFlags values are relevant. These flag + values are bit fields; therefore we can test whether + a bit position is set (1) or not set (0). +**************************************************************************/ +#define SFSD_FCB_IN_INIT (0x00000001) +#define SFSD_FCB_IN_TEARDOWN (0x00000002) +#define SFSD_FCB_PAGE_FILE (0x00000004) +#define SFSD_FCB_DIRECTORY (0x00000008) +#define SFSD_FCB_ROOT_DIRECTORY (0x00000018) +#define SFSD_FCB_WRITE_THROUGH (0x00000020) +#define SFSD_FCB_MAPPED (0x00000040) +#define SFSD_FCB_FAST_IO_READ_IN_PROGESS (0x00000080) +#define SFSD_FCB_FAST_IO_WRITE_IN_PROGESS (0x00000100) +#define SFSD_FCB_DELETE_ON_CLOSE (0x00000200) +#define SFSD_FCB_MODIFIED (0x00000400) +#define SFSD_FCB_ACCESSED (0x00000800) +#define SFSD_FCB_READ_ONLY (0x00001000) + +#define SFSD_INITIALIZED_MAIN_RESOURCE (0x00002000) +#define SFSD_INITIALIZED_PAGING_IO_RESOURCE (0x00004000) + +#define SFSD_FCB_NOT_FROM_ZONE (0x80000000) + +/************************************************************************** + A logical volume is represented using the following structure. + This structure is allocated as part of the device extension + for a device object that this sample FSD will create, to represent + the mounted logical volume. + + NOTE: If you were to extend this sample FSD to be a "real" FSD, + you would be worried about allocated clusters/sectiors, + bitmaps providing such information for the mounted volume, + dirty/modified clusters/sectiors etc. + This sample FSD does not maintain such information in the + in-memory VCB, though you may wish to consider it. +**************************************************************************/ +typedef struct _SFsdVolumeControlBlock { + SFsdIdentifier NodeIdentifier; + // a resource to protect the fields contained within the VCB + ERESOURCE VCBResource; + // each VCB is accessible off a global linked list + LIST_ENTRY NextVCB; + // each VCB points to a VPB structure created by the NT I/O Manager + PVPB PtrVPB; + // a set of flags that might mean something useful + uint32 VCBFlags; + // A count of the number of open files/directories + // As long as the count is != 0, the volume cannot + // be dismounted or locked. + uint32 VCBOpenCount; + // a global list of all FCB structures associated with the VCB + LIST_ENTRY NextFCB; + // we will maintain a global list of IRP's that are pending + // because of a directory notify request. + LIST_ENTRY NextNotifyIRP; + // the above list is protected only by the mutex declared below + KMUTEX NotifyIRPMutex; + // for each mounted volume, we create a device object. Here then + // is a back pointer to that device object + PDEVICE_OBJECT VCBDeviceObject; + // We also retain a pointer to the physical device object on which we + // have mounted ourselves. The I/O Manager passes us a pointer to this + // device object when requesting a mount operation. + PDEVICE_OBJECT TargetDeviceObject; + // the volume structure contains a pointer to the root directory FCB + PtrSFsdFCB PtrRootDirectoryFCB; + // the complete name of the user visible drive letter we serve + uint8 *PtrVolumePath; + // For volume open operations, we do not create a FCB (we use the VCB + // directly instead). Therefore, all CCB structures for the volume + // open operation are linked directly off the VCB + LIST_ENTRY VolumeOpenListHead; + // Pointer to a stream file object created for the volume information + // to be more easily read from secondary storage (with the support of + // the NT Cache Manager). + PFILE_OBJECT PtrStreamFileObject; + // Required to use the Cache Manager. + SECTION_OBJECT_POINTERS SectionObject; + // File sizes required to use the Cache Manager. + LARGE_INTEGER AllocationSize; + LARGE_INTEGER FileSize; + LARGE_INTEGER ValidDataLength; +} SFsdVCB, *PtrSFsdVCB; + +// some valid flags for the VCB +#define SFSD_VCB_FLAGS_VOLUME_MOUNTED (0x00000001) +#define SFSD_VCB_FLAGS_VOLUME_LOCKED (0x00000002) +#define SFSD_VCB_FLAGS_BEING_DISMOUNTED (0x00000004) +#define SFSD_VCB_FLAGS_SHUTDOWN (0x00000008) +#define SFSD_VCB_FLAGS_VOLUME_READ_ONLY (0x00000010) + +#define SFSD_VCB_FLAGS_VCB_INITIALIZED (0x00000020) + +/************************************************************************** + The IRP context encapsulates the current request. This structure is + used in the "common" dispatch routines invoked either directly in + the context of the original requestor, or indirectly in the context + of a system worker thread. +**************************************************************************/ +typedef struct _SFsdIrpContext { + SFsdIdentifier NodeIdentifier; + uint32 IrpContextFlags; + // copied from the IRP + uint8 MajorFunction; + // copied from the IRP + uint8 MinorFunction; + // to queue this IRP for asynchronous processing + WORK_QUEUE_ITEM WorkQueueItem; + // the IRP for which this context structure was created + PIRP Irp; + // the target of the request (obtained from the IRP) + PDEVICE_OBJECT TargetDeviceObject; + // if an exception occurs, we will store the code here + NTSTATUS SavedExceptionCode; +} SFsdIrpContext, *PtrSFsdIrpContext; + +#define SFSD_IRP_CONTEXT_CAN_BLOCK (0x00000001) +#define SFSD_IRP_CONTEXT_WRITE_THROUGH (0x00000002) +#define SFSD_IRP_CONTEXT_EXCEPTION (0x00000004) +#define SFSD_IRP_CONTEXT_DEFERRED_WRITE (0x00000008) +#define SFSD_IRP_CONTEXT_ASYNC_PROCESSING (0x00000010) +#define SFSD_IRP_CONTEXT_NOT_TOP_LEVEL (0x00000020) + +#define SFSD_IRP_CONTEXT_NOT_FROM_ZONE (0x80000000) + +/************************************************************************** + we will store all of our global variables in one structure. + Global variables are not specific to any mounted volume BUT + by definition are required for successful operation of the + FSD implementation. +**************************************************************************/ +typedef struct _SFsdData { + SFsdIdentifier NodeIdentifier; + // the fields in this list are protected by the following resource + ERESOURCE GlobalDataResource; + // each driver has a driver object created for it by the NT I/O Mgr. + // we are no exception to this rule. + PDRIVER_OBJECT SFsdDriverObject; + // we will create a device object for our FSD as well ... + // Although not really required, it helps if a helper application + // writen by us wishes to send us control information via + // IOCTL requests ... + PDEVICE_OBJECT SFsdDeviceObject; + // we will keep a list of all logical volumes for our sample FSD + LIST_ENTRY NextVCB; + // the NT Cache Manager, the I/O Manager and we will conspire + // to bypass IRP usage using the function pointers contained + // in the following structure + FAST_IO_DISPATCH SFsdFastIoDispatch; + // The NT Cache Manager uses the following call backs to ensure + // correct locking hierarchy is maintained + CACHE_MANAGER_CALLBACKS CacheMgrCallBacks; + // structures allocated from a zone need some fields here. Note + // that under version 4.0, it might be better to use lookaside + // lists + KSPIN_LOCK ZoneAllocationSpinLock; + ZONE_HEADER ObjectNameZoneHeader; + ZONE_HEADER CCBZoneHeader; + ZONE_HEADER FCBZoneHeader; + ZONE_HEADER ByteLockZoneHeader; + ZONE_HEADER IrpContextZoneHeader; + void *ObjectNameZone; + void *CCBZone; + void *FCBZone; + void *ByteLockZone; + void *IrpContextZone; + // currently, there is a single default zone size value used for + // all zones. This should ideally be changed by you to be 1 per + // type of zone (e.g. a default size for the FCB zone might be + // different from the default size for the ByteLock zone). + + // Of course, you will need to use different values (min/max) + // for lookaside lists (if you decide to use them instead) + uint32 DefaultZoneSizeInNumStructs; + // some state information is maintained in the flags field + uint32 SFsdFlags; + // Handle returned by the MUP is stored here. + HANDLE MupHandle; +} SFsdData, *PtrSFsdData; + +// valid flag values for the global data structure +#define SFSD_DATA_FLAGS_RESOURCE_INITIALIZED (0x00000001) +#define SFSD_DATA_FLAGS_ZONES_INITIALIZED (0x00000002) + +// a default size of the number of pages of non-paged pool allocated +// for each of the zones ... + +// Note that the values are absolutely arbitrary, the only information +// worth using from the values themselves is that they increase for +// larger systems (i.e. systems with more memory) +#define SFSD_DEFAULT_ZONE_SIZE_SMALL_SYSTEM (0x4) +#define SFSD_DEFAULT_ZONE_SIZE_MEDIUM_SYSTEM (0x8) +#define SFSD_DEFAULT_ZONE_SIZE_LARGE_SYSTEM (0xc) + +// another simplistic (brain dead ? :-) method used is to simply double +// the values for a "server" machine + +// So, for all you guys who "modified" the registry ;-) to change the +// wkstation into a server, tough luck ! +#define SFSD_NTAS_MULTIPLE (0x2) + +/*************************************************************************** +The following locking hierarchy is maintained in this sample filesystem +driver: + +(a) the global structure resource can be acquired at any time. However, + it is an "end resource" i.e. once acquired, no other resource can + be obtained until the global structure resource is released. +(b) the logical volume resource must be acquired (if required) before + any of the other resources are acquired. +(c) a file control block can be acquired next (if required). If two + FCB structures need to be acquired, the FCB "higher" in the directory + tree must be acquired first. + For a FCB, the "main resource" must be acquired first before a + "paging i/o" resource is acquired. + +Whenever a file is opened, the logical volume structure is referenced. +This ensures that the volume cannot be dismounted while any file is open. + +***************************************************************************/ + +#endif _SFSD_STRUCTURES_H_ // has this file been included? + + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/readme1.txt`: + +```txt +Sample File System Driver +------------------------ + +The sample file system driver code provided here can be used to +design and implement your own file system driver product. + +!!!!!!!!!!!!!!!!!!!!!!!WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +The code provided here is for illustration purposes only. It is not +to be used as-is. Substantial modifications, enhancements, and testing +are required before installing any portion of the code on any machine. + +DO NOT EXPECT THIS CODE TO WORK. IT IS ONLY TO BE USED AS A GUIDE +IN CONJUNCTION WITH THE TEXT CONTAINED IN THE BOOK IN DESIGNING YOUR +OWN FSD. IT IS SEVERELY LACKING IN FUNCTIONALITY AND HAS NEVER BEEN +INSTALLED OR TESTED AS IS COMMONLY REQUIRED. + +TO COMPILE THE SOURCES PROVIDED HERE, YOU MUST HAVE A COPY OF THE +"ntifs.h" HEADER FILE PROVIDED BY MICROSOFT AS PART OF THE WINDOWS NT +IFS KIT. + +!!!!!!!!!!!!!!!!!!!!!!!WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +Files provided: +-------------- + +src\sfsdinit.c - File system driver initialization functions +src\create.c - Routines to support create/open requests +src\cleanup.c - Stub functions for the cleanup dispatch entry point +src\close.c - Stub functions for the close dispatch entry point +src\dircntrl.c - Routines to support directory control requests +src\read.c - Support for the read dispatch routine +src\write.c - Support for the write dispatch routine +src\fastio.c - The fast I/O entry points (and Cache Manager callbacks) +src\flush.c - Support for the flush entry point +src\devcntrl.c - Device IOCTL support +src\fileinfo.c - Query/modify file attributes +src\shutdown.c - Support for the shutdown notification call +src\misc.c - Everything that didn't fit into the above, but was required + for compilation purposes +inc\protos.h - Prototypes for all functions in files listed above +inc\sfsd.h - The "main" include file; also contains useful macros +inc\struct.h - Structure type definitions +inc\errmsg.h - Automatically generated include file + (by the message compiler) that allows reporting of + FSD-specific error/warning/informational + messages in the event log (see the event subdirectory) +src\makefile - makefile! +src\sources - Contains list of files to be built; used by build.exe +README.TXT - What you are reading + + + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/src/cleanup.c`: + +```c +/************************************************************************* +* +* File: cleanup.c +* +* Module: Sample File System Driver (Kernel mode execution only) +* +* Description: +* Should contain code to handle the "Cleanup" dispatch entry point. +* This file serves as a placeholder. Please update this file as part +* of designing and implementing your FSD. +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +#include "sfsd.h" + +// define the file specific bug-check id +#define SFSD_BUG_CHECK_ID SFSD_FILE_CLEANUP + + + +/************************************************************************* +* +* Function: SFsdCleanup() +* +* Description: +* The I/O Manager will invoke this routine to handle a cleanup +* request +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL (invocation at higher IRQL will cause execution +* to be deferred to a worker thread context) +* +* Return Value: Does not matter! +* +*************************************************************************/ +NTSTATUS SFsdCleanup( +PDEVICE_OBJECT DeviceObject, // the logical volume device object +PIRP Irp) // I/O Request Packet +{ + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdIrpContext PtrIrpContext = NULL; + BOOLEAN AreWeTopLevel = FALSE; + + FsRtlEnterFileSystem(); + ASSERT(DeviceObject); + ASSERT(Irp); + + // set the top level context + AreWeTopLevel = SFsdIsIrpTopLevel(Irp); + + try { + + // get an IRP context structure and issue the request + PtrIrpContext = SFsdAllocateIrpContext(Irp, DeviceObject); + ASSERT(PtrIrpContext); + + RC = SFsdCommonCleanup(PtrIrpContext, Irp); + + } except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) { + + RC = SFsdExceptionHandler(PtrIrpContext, Irp); + + SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC); + } + + if (AreWeTopLevel) { + IoSetTopLevelIrp(NULL); + } + + FsRtlExitFileSystem(); + + return(RC); +} + + + +/************************************************************************* +* +* Function: SFsdCommonCleanup() +* +* Description: +* The actual work is performed here. This routine may be invoked in one' +* of the two possible contexts: +* (a) in the context of a system worker thread +* (b) in the context of the original caller +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: Does not matter! +* +*************************************************************************/ +NTSTATUS SFsdCommonCleanup( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp) +{ + NTSTATUS RC = STATUS_SUCCESS; + PIO_STACK_LOCATION PtrIoStackLocation = NULL; + PFILE_OBJECT PtrFileObject = NULL; + PtrSFsdFCB PtrFCB = NULL; + PtrSFsdCCB PtrCCB = NULL; + PtrSFsdVCB PtrVCB = NULL; + PtrSFsdNTRequiredFCB PtrReqdFCB = NULL; + PERESOURCE PtrResourceAcquired = NULL; + IO_STATUS_BLOCK LocalIoStatus; + + BOOLEAN CompleteIrp = TRUE; + BOOLEAN PostRequest = FALSE; + + BOOLEAN CanWait = FALSE; + + try { + // First, get a pointer to the current I/O stack location + PtrIoStackLocation = IoGetCurrentIrpStackLocation(PtrIrp); + ASSERT(PtrIoStackLocation); + + PtrFileObject = PtrIoStackLocation->FileObject; + ASSERT(PtrFileObject); + + // Get the FCB and CCB pointers + PtrCCB = (PtrSFsdCCB)(PtrFileObject->FsContext2); + ASSERT(PtrCCB); + PtrFCB = PtrCCB->PtrFCB; + ASSERT(PtrFCB); + + PtrVCB = (PtrSFsdVCB)(PtrIrpContext->TargetDeviceObject->DeviceExtension); + ASSERT(PtrVCB); + ASSERT(PtrVCB->NodeIdentifier.NodeType == SFSD_NODE_TYPE_VCB); + + // Steps you will probably take at this point are: + // (a) Acquire the VCB exclusively + // (b) Acquire the file (FCB) exclusively + // (c) Flush file data to disk + // (d) Talk to the FSRTL package (if you use it) about pending oplocks. + // (e) Notify the FSRTL package (if you use it) for use with pending + // notification IRPs + // (f) Unlock byte-range locks (if any were acquired by process) + // (g) Update time stamp values (e.g. fast-IO had been performed) + // (h) Inform the Cache Manager to uninitialize Cache Maps ... + // and other similar stuff. Chapter 10 in the book describes in further + // detail the list of functionality expected from a typical cleanup routine. + + try_exit: NOTHING; + + } finally { + + // See the read/write examples for how to fill in this portion + + } // end of "finally" processing + + return(RC); +} + + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/src/close.c`: + +```c +/************************************************************************* +* +* File: close.c +* +* Module: Sample File System Driver (Kernel mode execution only) +* +* Description: +* Should contain code to handle the "Close" dispatch entry point. +* This file serves as a placeholder. Please update this file as part +* of designing and implementing your FSD. +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +#include "sfsd.h" + +// define the file specific bug-check id +#define SFSD_BUG_CHECK_ID SFSD_FILE_CLOSE + + + +/************************************************************************* +* +* Function: SFsdClose() +* +* Description: +* The I/O Manager will invoke this routine to handle a close +* request +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL (invocation at higher IRQL will cause execution +* to be deferred to a worker thread context) +* +* Return Value: Does not matter! +* +*************************************************************************/ +NTSTATUS SFsdClose( +PDEVICE_OBJECT DeviceObject, // the logical volume device object +PIRP Irp) // I/O Request Packet +{ + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdIrpContext PtrIrpContext = NULL; + BOOLEAN AreWeTopLevel = FALSE; + + FsRtlEnterFileSystem(); + ASSERT(DeviceObject); + ASSERT(Irp); + + // set the top level context + AreWeTopLevel = SFsdIsIrpTopLevel(Irp); + + try { + + // get an IRP context structure and issue the request + PtrIrpContext = SFsdAllocateIrpContext(Irp, DeviceObject); + ASSERT(PtrIrpContext); + + RC = SFsdCommonClose(PtrIrpContext, Irp); + + } except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) { + + RC = SFsdExceptionHandler(PtrIrpContext, Irp); + + SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC); + } + + if (AreWeTopLevel) { + IoSetTopLevelIrp(NULL); + } + + FsRtlExitFileSystem(); + + return(RC); +} + + + + +/************************************************************************* +* +* Function: SFsdCommonClose() +* +* Description: +* The actual work is performed here. This routine may be invoked in one' +* of the two possible contexts: +* (a) in the context of a system worker thread +* (b) in the context of the original caller +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: Does not matter! +* +*************************************************************************/ +NTSTATUS SFsdCommonClose( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp) +{ + NTSTATUS RC = STATUS_SUCCESS; + PIO_STACK_LOCATION PtrIoStackLocation = NULL; + PFILE_OBJECT PtrFileObject = NULL; + PtrSFsdFCB PtrFCB = NULL; + PtrSFsdCCB PtrCCB = NULL; + PtrSFsdVCB PtrVCB = NULL; + PtrSFsdNTRequiredFCB PtrReqdFCB = NULL; + PERESOURCE PtrResourceAcquired = NULL; + IO_STATUS_BLOCK LocalIoStatus; + + BOOLEAN CompleteIrp = TRUE; + BOOLEAN PostRequest = FALSE; + + BOOLEAN CanWait = FALSE; + + try { + // First, get a pointer to the current I/O stack location + PtrIoStackLocation = IoGetCurrentIrpStackLocation(PtrIrp); + ASSERT(PtrIoStackLocation); + + PtrFileObject = PtrIoStackLocation->FileObject; + ASSERT(PtrFileObject); + + // Get the FCB and CCB pointers + PtrCCB = (PtrSFsdCCB)(PtrFileObject->FsContext2); + ASSERT(PtrCCB); + PtrFCB = PtrCCB->PtrFCB; + ASSERT(PtrFCB); + + PtrVCB = (PtrSFsdVCB)(PtrIrpContext->TargetDeviceObject->DeviceExtension); + ASSERT(PtrVCB); + ASSERT(PtrVCB->NodeIdentifier.NodeType == SFSD_NODE_TYPE_VCB); + + // Steps you will probably take at this point are: + // (a) Acquire the VCB exclusively + // (b) Acquire the file (FCB) exclusively + // (c) Delete the CCB structure (free memory) + // (d) If this is the last close, release the FCB structure (unless you keep these + // around for "delayed close" functionality. + // Note that it is often the case that the close dispatch entry point is invoked + // in the most inconvenient of situations (when it is not possible, for example, + // to safely acquire certain required resources without deadlocking or waiting). + // Therefore, be extremely careful in implementing this close dispatch entry point. + // Also note that you do not have the option of returning a failure code from the + // close dispatch entry point; the system expects that the close will always succeed. + + try_exit: NOTHING; + + } finally { + + // See the read/write examples for how to fill in this portion + + } // end of "finally" processing + + return(RC); +} + + + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/src/create.c`: + +```c +/************************************************************************* +* +* File: create.c +* +* Module: Sample File System Driver (Kernel mode execution only) +* +* Description: +* Contains code to handle the "Create"/"Open" dispatch entry point. +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +#include "sfsd.h" + +// define the file specific bug-check id +#define SFSD_BUG_CHECK_ID SFSD_FILE_CREATE + + + +/************************************************************************* +* +* Function: SFsdCreate() +* +* Description: +* The I/O Manager will invoke this routine to handle a create/open +* request +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL (invocation at higher IRQL will cause execution +* to be deferred to a worker thread context) +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFsdCreate( +PDEVICE_OBJECT DeviceObject, // the logical volume device object +PIRP Irp) // I/O Request Packet +{ + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdIrpContext PtrIrpContext; + BOOLEAN AreWeTopLevel = FALSE; + + FsRtlEnterFileSystem(); + ASSERT(DeviceObject); + ASSERT(Irp); + + // sometimes, we may be called here with the device object representing + // the file system (instead of the device object created for a logical + // volume. In this case, there is not much we wish to do (this create + // typically will happen 'cause some process has to open the FSD device + // object so as to be able to send an IOCTL to the FSD) + + // All of the logical volume device objects we create have a device + // extension whereas the device object representing the FSD has no + // device extension. This seems like a good enough method to identify + // between the two device objects ... + if (DeviceObject->Size == (unsigned short)(sizeof(DEVICE_OBJECT))) { + // this is an open of the FSD itself + Irp->IoStatus.Status = RC; + Irp->IoStatus.Information = FILE_OPENED; + + IoCompleteRequest(Irp, IO_NO_INCREMENT); + return(RC); + } + + // set the top level context + AreWeTopLevel = SFsdIsIrpTopLevel(Irp); + + try { + + // get an IRP context structure and issue the request + PtrIrpContext = SFsdAllocateIrpContext(Irp, DeviceObject); + ASSERT(PtrIrpContext); + + RC = SFsdCommonCreate(PtrIrpContext, Irp); + + } except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) { + + RC = SFsdExceptionHandler(PtrIrpContext, Irp); + + SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC); + } + + if (AreWeTopLevel) { + IoSetTopLevelIrp(NULL); + } + + FsRtlExitFileSystem(); + + return(RC); +} + + + +/************************************************************************* +* +* Function: SFsdCommonCreate() +* +* Description: +* The actual work is performed here. This routine may be invoked in one' +* of the two possible contexts: +* (a) in the context of a system worker thread +* (b) in the context of the original caller +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFsdCommonCreate( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp) +{ + NTSTATUS RC = STATUS_SUCCESS; + PIO_STACK_LOCATION PtrIoStackLocation = NULL; + PIO_SECURITY_CONTEXT PtrSecurityContext = NULL; + PFILE_OBJECT PtrNewFileObject = NULL; + PFILE_OBJECT PtrRelatedFileObject = NULL; + uint32 AllocationSize = 0; // if we create a new file + PFILE_FULL_EA_INFORMATION PtrExtAttrBuffer = NULL; + unsigned long RequestedOptions = 0; + unsigned long RequestedDisposition = 0; + uint8 FileAttributes = 0; + unsigned short ShareAccess = 0; + unsigned long ExtAttrLength = 0; + ACCESS_MASK DesiredAccess; + + BOOLEAN DeferredProcessing = FALSE; + + PtrSFsdVCB PtrVCB = NULL; + BOOLEAN AcquiredVCB = FALSE; + + BOOLEAN DirectoryOnlyRequested = FALSE; + BOOLEAN FileOnlyRequested = FALSE; + BOOLEAN NoBufferingSpecified = FALSE; + BOOLEAN WriteThroughRequested = FALSE; + BOOLEAN DeleteOnCloseSpecified = FALSE; + BOOLEAN NoExtAttrKnowledge = FALSE; + BOOLEAN CreateTreeConnection = FALSE; + BOOLEAN OpenByFileId = FALSE; + + // Are we dealing with a page file? + BOOLEAN PageFileManipulation = FALSE; + + // Is this open for a target directory (used in rename operations)? + BOOLEAN OpenTargetDirectory = FALSE; + + // Should we ignore case when attempting to locate the object? + BOOLEAN IgnoreCaseWhenChecking = FALSE; + + PtrSFsdCCB PtrRelatedCCB = NULL, PtrNewCCB = NULL; + PtrSFsdFCB PtrRelatedFCB = NULL, PtrNewFCB = NULL; + + unsigned long ReturnedInformation; + + UNICODE_STRING TargetObjectName; + UNICODE_STRING RelatedObjectName; + + UNICODE_STRING AbsolutePathName; + + LARGE_INTEGER FileAllocationSize, FileEndOfFile; + + + ASSERT(PtrIrpContext); + ASSERT(PtrIrp); + + try { + + AbsolutePathName.Buffer = NULL; + AbsolutePathName.Length = AbsolutePathName.MaximumLength = 0; + + // First, get a pointer to the current I/O stack location + PtrIoStackLocation = IoGetCurrentIrpStackLocation(PtrIrp); + ASSERT(PtrIoStackLocation); + + // If the caller cannot block, post the request to be handled + // asynchronously + if (!(PtrIrpContext->IrpContextFlags & SFSD_IRP_CONTEXT_CAN_BLOCK)) { + // We must defer processing of this request since we could + // block anytime while performing the create/open ... + RC = SFsdPostRequest(PtrIrpContext, PtrIrp); + DeferredProcessing = TRUE; + try_return(RC); + } + + // Now, we can obtain the parameters specified by the user. + // Note that the file object is the new object created by the + // I/O Manager in anticipation that this create/open request + // will succeed. + PtrNewFileObject = PtrIoStackLocation->FileObject; + TargetObjectName = PtrNewFileObject->FileName; + PtrRelatedFileObject = PtrNewFileObject->RelatedFileObject; + + // If a related file object is present, get the pointers + // to the CCB and the FCB for the related file object + if (PtrRelatedFileObject) { + PtrRelatedCCB = (PtrSFsdCCB)(PtrRelatedFileObject->FsContext2); + ASSERT(PtrRelatedCCB); + ASSERT(PtrRelatedCCB->NodeIdentifier.NodeType == SFSD_NODE_TYPE_CCB); + // each CCB in turn points to a FCB + PtrRelatedFCB = PtrRelatedCCB->PtrFCB; + ASSERT(PtrRelatedFCB); + ASSERT((PtrRelatedFCB->NodeIdentifier.NodeType == SFSD_NODE_TYPE_FCB) + || + (PtrRelatedFCB->NodeIdentifier.NodeType == SFSD_NODE_TYPE_VCB)); + RelatedObjectName = PtrRelatedFileObject->FileName; + } + + // Allocation size is only used if a new file is created + // or a file is superseded. + AllocationSize = PtrIrp->Overlay.AllocationSize.LowPart; + // Note: Some FSD implementations support file sizes > 2 GB. + // The following check is only valid if your FSD does not support + // a large file size. With NT version 5.0, 64 bit support will + // become available and your FSD should ideally support large files ... + if (PtrIrp->Overlay.AllocationSize.HighPart) { + RC = STATUS_INVALID_PARAMETER; + try_return(RC); + } + + // Get a ptr to the supplied security context + PtrSecurityContext = PtrIoStackLocation->Parameters.Create.SecurityContext; + + // The desired access can be obtained from the SecurityContext + DesiredAccess = PtrSecurityContext->DesiredAccess; + + // Two values are supplied in the Create.Options field: + // (a) the actual user supplied options + // (b) the create disposition + RequestedOptions = (PtrIoStackLocation->Parameters.Create.Options & FILE_VALID_OPTION_FLAGS); + + // The file disposition is packed with the user options ... + // Disposition includes FILE_SUPERSEDE, FILE_OPEN_IF, etc. + RequestedDisposition = ((PtrIoStackLocation->Parameters.Create.Options >> 24) && 0xFF); + + FileAttributes = (uint8)(PtrIoStackLocation->Parameters.Create.FileAttributes & FILE_ATTRIBUTE_VALID_FLAGS); + ShareAccess = PtrIoStackLocation->Parameters.Create.ShareAccess; + + // If your FSD does not support EA manipulation, you might return + // invalid parameter if the following are supplied. + // EA arguments are only used if a new file is created or a file is + // superseded + PtrExtAttrBuffer = PtrIrp->AssociatedIrp.SystemBuffer; + ExtAttrLength = PtrIoStackLocation->Parameters.Create.EaLength; + + // Get the options supplied by the user + + // User specifies that returned object MUST be a directory. + // Lack of presence of this flag does not mean it *cannot* be a + // directory *unless* FileOnlyRequested is set (see below) + + // Presence of the flag however, does require that the returned object be + // a directory (container) object. + DirectoryOnlyRequested = ((RequestedOptions & FILE_DIRECTORY_FILE) ? TRUE : FALSE); + + // User specifies that returned object MUST NOT be a directory. + // Lack of presence of this flag does not mean it *cannot* be a + // file *unless* DirectoryOnlyRequested is set (see above). + + // Presence of the flag however does require that the returned object be + // a simple file (non-container) object. + FileOnlyRequested = ((RequestedOptions & FILE_NON_DIRECTORY_FILE) ? TRUE : FALSE); + + // We cannot cache the file if the following flag is set. + // However, things do get a little bit interesting if caching + // has been already initiated due to a previous open ... + // (maintaining consistency then becomes a little bit more + // of a headache - see read/write file descriptions) + NoBufferingSpecified = ((RequestedOptions & FILE_NO_INTERMEDIATE_BUFFERING) ? TRUE : FALSE); + + // Write-through simply means that the FSD must *not* return from + // a user write request until the data has been flushed to secondary + // storage (either to disks directly connected to the node or across + // the network in the case of a redirector) + WriteThroughRequested = ((RequestedOptions & FILE_WRITE_THROUGH) ? TRUE : FALSE); + + // Not all of the native file system implementations support + // the delete-on-close option. All this means is that after the + // last close on the FCB has been performed, your FSD should + // delete the file. It simply saves the caller from issuing a + // separate delete request. Also, some FSD implementations might choose + // to implement a Windows NT idiosyncratic behavior wherein you + // could create such "delete-on-close" marked files under directories + // marked for deletion. Ordinarily, a FSD will not allow you to create + // a new file under a directory that has been marked for deletion. + DeleteOnCloseSpecified = ((RequestedOptions & FILE_DELETE_ON_CLOSE) ? TRUE : FALSE); + + NoExtAttrKnowledge = ((RequestedOptions & FILE_NO_EA_KNOWLEDGE) ? TRUE : FALSE); + + // The following flag is only used by the LAN Manager redirector + // to initiate a "new mapping" to a remote share. Typically, + // a FSD will not see this flag (especially disk based FSD's) + CreateTreeConnection = ((RequestedOptions & FILE_CREATE_TREE_CONNECTION) ? TRUE : FALSE); + + // The NTFS file system for exmaple supports the OpenByFileId option. + // Your FSD may also be able to associate a unique numerical ID with + // an on-disk object. The caller would get this ID in a "query file + // information" call. + + // Later, the caller might decide to reopen the object, this time + // though it may supply your FSD with the file identifier instead of + // a file/path name. + OpenByFileId = ((RequestedOptions & FILE_OPEN_BY_FILE_ID) ? TRUE : FALSE); + + // Are we dealing with a page file? + PageFileManipulation = ((PtrIoStackLocation->Flags & SL_OPEN_PAGING_FILE) ? TRUE : FALSE); + + // The open target directory flag is used as part of the sequence of + // operations performed by the I/O Manager is response to a file/dir + // rename operation. See the explanation in the book for details. + OpenTargetDirectory = ((PtrIoStackLocation->Flags & SL_OPEN_TARGET_DIRECTORY) ? TRUE : FALSE); + + // If your FSD supports case-sensitive file name checks, you may + // choose to honor the following flag ... + IgnoreCaseWhenChecking = ((PtrIoStackLocation->Flags & SL_CASE_SENSITIVE) ? TRUE : FALSE); + + // Ensure that the operation has been directed to a valid VCB ... + PtrVCB = (PtrSFsdVCB)(PtrIrpContext->TargetDeviceObject->DeviceExtension); + ASSERT(PtrVCB); + ASSERT(PtrVCB->NodeIdentifier.NodeType == SFSD_NODE_TYPE_VCB); + + // Use coarse grained locking and acquire the VCB exclusively. This + // will lock out all other concurrent create/open requests + // If the VCB is also acquired by the cleanup/close dispatch routines, + // you can count on create/opens being synchronized with these functions + // as well. + ExAcquireResourceExclusiveLite(&(PtrVCB->VCBResource), TRUE); + AcquiredVCB = TRUE; + + // Disk based file systems might decide to verify the logical volume + // (if required and only if removable media are supported) at this time + + // Implement your own volume verification routine ... + // Read the DDK for more information on when a FSD must verify a + // volume ... + // if (!NT_SUCCESS(RC = SFsdVerifyVolume(PtrVCB))) { + // try_return(RC); + // } + + // If the volume has been locked, fail the request + if (PtrVCB->VCBFlags & SFSD_VCB_FLAGS_VOLUME_LOCKED) { + RC = STATUS_ACCESS_DENIED; + try_return(RC); + } + + // If a volume open is requested, satisfy it now + if ((PtrNewFileObject->FileName.Length == 0) && ((PtrRelatedFileObject == NULL) || + (PtrRelatedFCB->NodeIdentifier.NodeType == SFSD_NODE_TYPE_VCB))) { + // If the supplied file name is NULL *and* either there exists + // no related file object *or* if a related file object was supplied + // but it too refers to a previously opened instance of a logical + // volume, this open must be for a logical volume. + + // Note: your FSD might decide to do "special" things (whatever they + // might be) in response to an open request for the logical volume. + + // Logical volume open requests are done primarily to get/set volume + // information, lock the volume, dismount the volume (using the IOCTL + // FSCTL_DISMOUNT_VOLUME) etc. + + // If a volume open is requested, perform checks to ensure that + // invalid options have not also been specified ... + if ((OpenTargetDirectory) || (PtrExtAttrBuffer)) { + RC = STATUS_INVALID_PARAMETER; + try_return(RC); + } + + if (DirectoryOnlyRequested) { + // a volume is not a directory + RC = STATUS_NOT_A_DIRECTORY; + try_return(RC); + } + + if ((RequestedDisposition != FILE_OPEN) && (RequestedDisposition != FILE_OPEN_IF)) { + // cannot create a new volume, I'm afraid ... + RC = STATUS_ACCESS_DENIED; + try_return(RC); + } + + RC = SFsdOpenVolume(PtrVCB, PtrIrpContext, PtrIrp, ShareAccess, PtrSecurityContext, PtrNewFileObject); + ReturnedInformation = PtrIrp->IoStatus.Information; + + try_return(RC); + } + + // Your FSD might wish to implement the open-by-id option. The "id" + // is some unique numerical representation of the on-disk object. + // The caller then therefore give you this file id and your FSD + // should be completely capable of "opening" the object (it must + // exist since the caller received an id for the object from your + // FSD in a "query file" call ... + + // If the file has been deleted in the meantime, you can return + // "not found" + if (OpenByFileId) { + // perform the open ... + // RC = SFsdOpenByFileId(PtrIrpContext, PtrIrp ....); + // try_return(RC); + } + + // Now determine the starting point from which to begin the parsing + if (PtrRelatedFileObject) { + // We have a user supplied related file object. + // This implies a "relative" open i.e. relative to the directory + // represented by the related file object ... + + // Note: The only purpose FSD implementations ever have for + // the related file object is to determine whether this + // is a relative open or not. At all other times (including + // during I/O operations), this field is meaningless from + // the FSD's perspective. + if (!(PtrRelatedFCB->FCBFlags & SFSD_FCB_DIRECTORY)) { + // we must have a directory as the "related" object + RC = STATUS_INVALID_PARAMETER; + try_return(RC); + } + + // So we have a directory, ensure that the name begins with + // a "\" i.e. begins at the root and does *not* begin with a "\\" + // NOTE: This is just an example of the kind of path-name string + // validation that a FSD must do. Although the remainder of + // the code may not include such checks, any commercial + // FSD *must* include such checking (no one else, including + // the I/O Manager will perform checks on your FSD's behalf) + if ((RelatedObjectName.Length == 0) || (RelatedObjectName.Buffer[0] != L'\\')) { + RC = STATUS_INVALID_PARAMETER; + try_return(RC); + } + + // similarly, if the target file name starts with a "\", it + // is wrong since the target file name can no longer be absolute + if ((TargetObjectName.Length != 0) && (TargetObjectName.Buffer[0] == L'\\')) { + RC = STATUS_INVALID_PARAMETER; + try_return(RC); + } + + // Create an absolute path-name. You could potentially use + // the absolute path-name if you cache previously opened + // file/directory object names. + { + AbsolutePathName.MaximumLength = TargetObjectName.Length + RelatedObjectName.Length + sizeof(WCHAR); + if (!(AbsolutePathName.Buffer = ExAllocatePool(PagedPool, AbsolutePathName.MaximumLength))) { + RC = STATUS_INSUFFICIENT_RESOURCES; + try_return(RC); + } + + RtlZeroMemory(AbsolutePathName.Buffer, AbsolutePathName.MaximumLength); + + RtlCopyMemory((void *)(AbsolutePathName.Buffer), (void *)(RelatedObjectName.Buffer), RelatedObjectName.Length); + AbsolutePathName.Length = RelatedObjectName.Length; + RtlAppendUnicodeToString(&AbsolutePathName, L"\\"); + RtlAppendUnicodeToString(&AbsolutePathName, TargetObjectName.Buffer); + } + + } else { + + // The suplied path-name must be an absolute path-name i.e. + // starting at the root of the file system tree + if (TargetObjectName.Buffer[0] != L'\\') { + RC = STATUS_INVALID_PARAMETER; + try_return(RC); + } + + { + AbsolutePathName.MaximumLength = TargetObjectName.Length; + if (!(AbsolutePathName.Buffer = ExAllocatePool(PagedPool, AbsolutePathName.MaximumLength))) { + RC = STATUS_INSUFFICIENT_RESOURCES; + try_return(RC); + } + + RtlZeroMemory(AbsolutePathName.Buffer, AbsolutePathName.MaximumLength); + + RtlCopyMemory((void *)(AbsolutePathName.Buffer), (void *)(TargetObjectName.Buffer), TargetObjectName.Length); + AbsolutePathName.Length = TargetObjectName.Length; + } + } + + // go into a loop parsing the supplied name + + // Use the algorithm supplied in the book to implement this + // loop. + + // Note that you may have to "open" intermediate directory objects + // while traversing the path. You should try to reuse existing code + // whenever possible therefore you should consider using a common + // open routine regardless of whether the open is on behalf of the + // caller or an intermediate (internal) open performed by your driver. + + // But first, check if the caller simply wishes to open the root + // of the file system tree. + if (AbsolutePathName.Length == 2) { + // this is an open of the root directory, ensure that the caller has not requested a file only + if (FileOnlyRequested || (RequestedDisposition == FILE_SUPERSEDE) || (RequestedDisposition == FILE_OVERWRITE) || + (RequestedDisposition == FILE_OVERWRITE_IF)) { + RC = STATUS_FILE_IS_A_DIRECTORY; + try_return(RC); + } + + // Insert code to open root directory here. Include creation of a new CCB structure. + // e.g. RC = SFsdOpenRootDirectory(...); + + try_return(RC); + } + + if (PtrRelatedFileObject) { + // Insert code such that your "start directory" is + // the one identified by the related file object + } else { + // Insert code to start at the root of the file system + } + + // NOTE: If your FSD does not support access checking i.e. + // your FSD does not check "traversal" privileges, + // you could easily maintain a "prefix" cache containing + // path-names and open FCB pointers. Then, if the requested + // path-name is already present in the cache i.e. someone + // had opened it earlier, you can avoid the tedious traversal + // of the entire path-name performed below and described in + // the book ... + + // If you do not maintain such a prefix table cache of previously + // opened object names or if you do not find the name to be opened + // in the cache, then: + + // Get the "next" component in the name to be parsed. Note that + // obtaining the next string component is similar to the strtok + // library routine where the separator is a "\" + + // Your FSD should also always check the validity of the token + // to ensure that only valid characters comprise the path/file name + + // Insert code to open the starting directory here. + + while (TRUE) { + // Insert code to perform the following tasks here: + + // (a) acquire the parent directory FCB MainResource exclusively + // (b) ensure that the parent directory in which you will perform + // a lookup operation is indeed a directory + // (c) if there are no more components left after this one in the + // pathname supplied by the user, break; + // (d) attempt to lookup the sub-directory in the parent + // (e) if not found, return STATUS_OBJECT_PATH_NOT_FOUND + // (f) Otherwise, open the new sub-directory and make it + // the "parent" + // (g) go back and repeat the loop for the next component in + // the path + + // NOTE: If your FSD supports it, you should always check + // that the caller has appropriate privileges to traverse + // the directories being searched. + } + + // Now we are down to the last component, check it out to see if it exists ... + + // If "open target directory" was specified + if (OpenTargetDirectory) { + if (NT_SUCCESS(RC)) { + // file exists, set this information in the Information field + ReturnedInformation = FILE_EXISTS; + } else { + RC = STATUS_SUCCESS; + + // Tell the I/O Manager that file does not exit + ReturnedInformation = FILE_DOES_NOT_EXIST; + } + + // Now, do the following: + + // (a) Replace the string in the FileName field in the + // PtrNewFileObject to identify the "target name" + // only (sans the path leading to the object) + { + unsigned int Index = (AbsolutePathName.Length - 1); + + // Back up until we come to the last '\' + // But first, skip any trailing '\' characters + + while (AbsolutePathName.Buffer[Index] == L'\\') { + ASSERT(Index >= sizeof(WCHAR)); + Index -= sizeof(WCHAR); + // Skip this length also + PtrNewFileObject->FileName.Length -= sizeof(WCHAR); + } + + while (AbsolutePathName.Buffer[Index] != L'\\') { + // Keep backing up until we hit one + ASSERT(Index >= sizeof(WCHAR)); + Index -= sizeof(WCHAR); + } + + // We must be at a '\' character + ASSERT(AbsolutePathName.Buffer[Index] == L'\\'); + Index++; + + // We can now determine the new length of the filename + // and copy the name over + PtrNewFileObject->FileName.Length -= (unsigned short)(Index*sizeof(WCHAR)); + RtlCopyMemory(&(PtrNewFileObject->FileName.Buffer[0]), + &(PtrNewFileObject->FileName.Buffer[Index]), + PtrNewFileObject->FileName.Length); + } + + // (b) Return with the target's parent directory opened + // (c) Update the file object FsContext and FsContext2 fields + // to reflect the fact that the parent directory of the + // target has been opened + + try_return(RC); + } + + if (!NT_SUCCESS(RC)) { + // Object was not found, create if requested + if ((RequestedDisposition == FILE_CREATE) || (RequestedDisposition == FILE_OPEN_IF) || + (RequestedDisposition == FILE_OVERWRITE_IF)) { + // Create a new file/directory here ... + // Note that a FCB structure will be allocated at this time + // and so will a CCB structure. Assume that these are called + // PtrNewFCB and PtrNewCCB respectively. + // Further, note that since the file is being created, no other + // thread can have the file stream open at this time. + + // Open the newly created object + + // Set the allocation size for the object is specified + + // Set extended attributes for the file ... + + // Set the Share Access for the file stream. + // The FCBShareAccess field will be set by the I/O Manager. + IoSetShareAccess(DesiredAccess, ShareAccess, PtrNewFileObject, &(PtrNewFCB->FCBShareAccess)); + + RC = STATUS_SUCCESS; + ReturnedInformation = FILE_CREATED; + } + + try_return(RC); + + } else { + if (RequestedDisposition == FILE_CREATE) { + ReturnedInformation = FILE_EXISTS; + RC = STATUS_OBJECT_NAME_COLLISION; + try_return(RC); + } + + // Insert code to open the target here, return if failed + + // The FSD will allocate a new FCB structure if no such structure + // currently exists in memory for the file stream. + // A new CCB will always be allocated. + // Assume that these structures are named PtrNewFCB and PtrNewCCB + // respectively. + // Further, you should obtain the FCB MainResource exclusively + // at this time. + + // Check if caller wanted a directory only and target object + // not a directory, or caller wanted a file only and target + // object not a file ... + if (FileOnlyRequested && (PtrNewFCB->FCBFlags & SFSD_FCB_DIRECTORY)) { + // Close the new FCB and leave. + // SFsdCloseCCB(PtrNewCCB); + RC = STATUS_FILE_IS_A_DIRECTORY; + try_return(RC); + } + + if ((PtrNewFCB->FCBFlags & SFSD_FCB_DIRECTORY) && ((RequestedDisposition == FILE_SUPERSEDE) || + (RequestedDisposition == FILE_OVERWRITE) || (RequestedDisposition == FILE_OVERWRITE_IF))) { + RC = STATUS_FILE_IS_A_DIRECTORY; + try_return(RC); + } + + if (DirectoryOnlyRequested && !(PtrNewFCB->FCBFlags & SFSD_FCB_DIRECTORY)) { + // Close the new FCB and leave. + // SFsdCloseCCB(PtrNewCCB); + RC = STATUS_NOT_A_DIRECTORY; + try_return(RC); + } + + // Check share access and fail if the share conflicts with an existing + // open. + if (PtrNewFCB->OpenHandleCount > 0) { + // The FCB is currently in use by some thread. + // We must check whether the requested access/share access + // conflicts with the existing open operations. + + if (!NT_SUCCESS(RC = IoCheckShareAccess(DesiredAccess, ShareAccess, PtrNewFileObject, + &(PtrNewFCB->FCBShareAccess), TRUE))) { + // SFsdCloseCCB(PtrNewCCB); + try_return(RC); + } + } else { + IoSetShareAccess(DesiredAccess, ShareAccess, PtrNewFileObject, &(PtrNewFCB->FCBShareAccess)); + } + + ReturnedInformation = FILE_OPENED; + + // If a supersede or overwrite was requested, do so now ... + if (RequestedDisposition == FILE_SUPERSEDE) { + // Attempt the operation here ... + // RC = SFsdSupersede(...); + if (NT_SUCCESS(RC)) { + ReturnedInformation = FILE_SUPERSEDED; + } + } else if ((RequestedDisposition == FILE_OVERWRITE) || (RequestedDisposition == FILE_OVERWRITE_IF)){ + // Attempt the operation here ... + // RC = SFsdOverwrite(...); + if (NT_SUCCESS(RC)) { + ReturnedInformation = FILE_OVERWRITTEN; + } + } + } + + try_exit: NOTHING; + + } finally { + // Complete the request unless we are here as part of unwinding + // when an exception condition was encountered, OR + // if the request has been deferred (i.e. posted for later handling) + if (RC != STATUS_PENDING) { + // If we acquired any FCB resources, release them now ... + + // If any intermediate (directory) open operations were performed, + // implement the corresponding close (do *not* however close + // the target you have opened on behalf of the caller ...). + + if (NT_SUCCESS(RC)) { + // Update the file object such that: + // (a) the FsContext field points to the NTRequiredFCB field + // in the FCB + // (b) the FsContext2 field points to the CCB created as a + // result of the open operation + + // If write-through was requested, then mark the file object + // appropriately + if (WriteThroughRequested) { + PtrNewFileObject->Flags |= FO_WRITE_THROUGH; + } + + } else { + // Perform failure related post-processing now + } + + // As long as this unwinding is not being performed as a result of + // an exception condition, complete the IRP ... + if (!(PtrIrpContext->IrpContextFlags & SFSD_IRP_CONTEXT_EXCEPTION)) { + PtrIrp->IoStatus.Status = RC; + PtrIrp->IoStatus.Information = ReturnedInformation; + + // Free up the Irp Context + SFsdReleaseIrpContext(PtrIrpContext); + + // complete the IRP + IoCompleteRequest(PtrIrp, IO_DISK_INCREMENT); + } + } + + if (AcquiredVCB) { + ASSERT(PtrVCB); + SFsdReleaseResource(&(PtrVCB->VCBResource)); + AcquiredVCB = FALSE; + } + + if (AbsolutePathName.Buffer != NULL) { + ExFreePool(AbsolutePathName.Buffer); + } + } + + return(RC); +} + + +/************************************************************************* +* +* Function: SFsdOpenVolume() +* +* Description: +* Open a logical volume for the caller. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFsdOpenVolume( +PtrSFsdVCB PtrVCB, // volume to be opened +PtrSFsdIrpContext PtrIrpContext, // IRP context +PIRP PtrIrp, // original/user IRP +unsigned short ShareAccess, // share access +PIO_SECURITY_CONTEXT PtrSecurityContext, // caller's context (incl access) +PFILE_OBJECT PtrNewFileObject) // I/O Mgr. created file object +{ + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdCCB PtrCCB = NULL; + + try { + // check for exclusive open requests (using share modes supplied) + // and determine whether it is even possible to open the volume + // with the specified share modes (e.g. if caller does not + // wish to share read or share write ...) + + // Use IoCheckShareAccess() and IoSetShareAccess() here ... + // They are defined in the DDK. + + // You might also wish to check the caller's security context + // to see whether you wish to allow the volume open or not. + // Use the SeAccessCheck() routine described in the DDK for this purpose. + + // create a new CCB structure + if (!(PtrCCB = SFsdAllocateCCB())) { + RC = STATUS_INSUFFICIENT_RESOURCES; + try_return(RC); + } + + // initialize the CCB + PtrCCB->PtrFCB = (PtrSFsdFCB)(PtrVCB); + InsertTailList(&(PtrVCB->VolumeOpenListHead), &(PtrCCB->NextCCB)); + + // initialize the CCB to point to the file object + PtrCCB->PtrFileObject = PtrNewFileObject; + + SFsdSetFlag(PtrCCB->CCBFlags, SFSD_CCB_VOLUME_OPEN); + + // initialize the file object appropriately + PtrNewFileObject->FsContext = (void *)(PtrVCB); + PtrNewFileObject->FsContext2 = (void *)(PtrCCB); + + // increment the number of outstanding open operations on this + // logical volume (i.e. volume cannot be dismounted) + + // You might be concerned about 32 bit wrap-around though I would + // argue that it is unlikely ... :-) + (PtrVCB->VCBOpenCount)++; + + // now set the IoStatus Information value correctly in the IRP + // (caller will set the status field) + PtrIrp->IoStatus.Information = FILE_OPENED; + + try_exit: NOTHING; + } finally { + NOTHING; + } + + return(RC); +} + + +/************************************************************************* +* +* Function: SFsdInitializeFCB() +* +* Description: +* Initialize a new FCB structure and also the sent-in file object +* (if supplied) +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: None +* +*************************************************************************/ +void SFsdInitializeFCB( +PtrSFsdFCB PtrNewFCB, // FCB structure to be initialized +PtrSFsdVCB PtrVCB, // logical volume (VCB) pointer +PtrSFsdObjectName PtrObjectName, // name of the object +uint32 Flags, // is this a file/directory, etc. +PFILE_OBJECT PtrFileObject) // optional file object to be initialized +{ + // Initialize the disk dependent portion as you see fit + + // Initialize the two ERESOURCE objects + ExInitializeResourceLite(&(PtrNewFCB->NTRequiredFCB.MainResource)); + ExInitializeResourceLite(&(PtrNewFCB->NTRequiredFCB.PagingIoResource)); + + PtrNewFCB->PtrVCB = PtrVCB; + + // caller MUST ensure that VCB has been acquired exclusively + InsertTailList(&(PtrVCB->NextFCB), &(PtrNewFCB->NextFCB)); + + // initialize the various list heads + InitializeListHead(&(PtrNewFCB->NextCCB)); + + PtrNewFCB->ReferenceCount = 1; + PtrNewFCB->OpenHandleCount = 1; + + PtrNewFCB->FCBFlags = Flags; + + PtrNewFCB->FCBName = PtrObjectName; + + if (PtrFileObject) { + PtrFileObject->FsContext = (void *)(&(PtrNewFCB->NTRequiredFCB)); + } + + return; +} + + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/src/devcntrl.c`: + +```c +/************************************************************************* +* +* File: devcntrl.c +* +* Module: Sample File System Driver (Kernel mode execution only) +* +* Description: +* Contains code to handle the "Device IOCTL" dispatch entry point. +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +#include "sfsd.h" + +// define the file specific bug-check id +#define SFSD_BUG_CHECK_ID SFSD_FILE_DEVICE_CONTROL + +#if(_WIN32_WINNT < 0x0400) +#define IOCTL_REDIR_QUERY_PATH CTL_CODE(FILE_DEVICE_NETWORK_FILE_SYSTEM, 99, METHOD_NEITHER, FILE_ANY_ACCESS) + +typedef struct _QUERY_PATH_REQUEST { + ULONG PathNameLength; + PIO_SECURITY_CONTEXT SecurityContext; + WCHAR FilePathName[1]; +} QUERY_PATH_REQUEST, *PQUERY_PATH_REQUEST; + +typedef struct _QUERY_PATH_RESPONSE { + ULONG LengthAccepted; +} QUERY_PATH_RESPONSE, *PQUERY_PATH_RESPONSE; +#endif + + +/************************************************************************* +* +* Function: SFsdDeviceControl() +* +* Description: +* The I/O Manager will invoke this routine to handle a Device IOCTL +* request +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL (invocation at higher IRQL will cause execution +* to be deferred to a worker thread context) +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFsdDeviceControl( +PDEVICE_OBJECT DeviceObject, // the logical volume device object +PIRP Irp) // I/O Request Packet +{ + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdIrpContext PtrIrpContext = NULL; + BOOLEAN AreWeTopLevel = FALSE; + + FsRtlEnterFileSystem(); + ASSERT(DeviceObject); + ASSERT(Irp); + + // set the top level context + AreWeTopLevel = SFsdIsIrpTopLevel(Irp); + + try { + + // get an IRP context structure and issue the request + PtrIrpContext = SFsdAllocateIrpContext(Irp, DeviceObject); + ASSERT(PtrIrpContext); + + RC = SFsdCommonDeviceControl(PtrIrpContext, Irp); + + } except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) { + + RC = SFsdExceptionHandler(PtrIrpContext, Irp); + + SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC); + } + + if (AreWeTopLevel) { + IoSetTopLevelIrp(NULL); + } + + FsRtlExitFileSystem(); + + return(RC); +} + + + +/************************************************************************* +* +* Function: SFsdCommonDeviceControl() +* +* Description: +* The actual work is performed here. This routine may be invoked in one' +* of the two possible contexts: +* (a) in the context of a system worker thread +* (b) in the context of the original caller +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFsdCommonDeviceControl( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp) +{ + NTSTATUS RC = STATUS_SUCCESS; + PIO_STACK_LOCATION PtrIoStackLocation = NULL; + PIO_STACK_LOCATION PtrNextIoStackLocation = NULL; + PFILE_OBJECT PtrFileObject = NULL; + PtrSFsdFCB PtrFCB = NULL; + PtrSFsdCCB PtrCCB = NULL; + PtrSFsdVCB PtrVCB = NULL; + BOOLEAN CompleteIrp = FALSE; + ULONG IoControlCode = 0; + void *BufferPointer = NULL; + + try { + // First, get a pointer to the current I/O stack location + PtrIoStackLocation = IoGetCurrentIrpStackLocation(PtrIrp); + ASSERT(PtrIoStackLocation); + + PtrFileObject = PtrIoStackLocation->FileObject; + ASSERT(PtrFileObject); + + PtrCCB = (PtrSFsdCCB)(PtrFileObject->FsContext2); + ASSERT(PtrCCB); + PtrFCB = PtrCCB->PtrFCB; + ASSERT(PtrFCB); + + if (PtrFCB->NodeIdentifier.NodeType == SFSD_NODE_TYPE_VCB) { + PtrVCB = (PtrSFsdVCB)(PtrFCB); + } else { + PtrVCB = PtrFCB->PtrVCB; + } + + // Get the IoControlCode value + IoControlCode = PtrIoStackLocation->Parameters.DeviceIoControl.IoControlCode; + + // You may wish to allow only volume open operations. + + switch (IoControlCode) { +#ifdef __THIS_IS_A_NETWORK_REDIR_ + case IOCTL_REDIR_QUERY_PATH: + // Only for network redirectors. + BufferPointer = (void *)(PtrIoStackLocation->Parameters.DeviceIoControl.Type3InputBuffer); + // Invoke the handler for this IOCTL. + RC = SFsdHandleQueryPath(BufferPointer); + CompleteIrp = TRUE; + try_return(RC); + break; +#endif // _THIS_IS_A_NETWORK_REDIR_ + default: + // Invoke the lower level driver in the chain. + PtrNextIoStackLocation = IoGetNextIrpStackLocation(PtrIrp); + *PtrNextIoStackLocation = *PtrIoStackLocation; + // Set a completion routine. + IoSetCompletionRoutine(PtrIrp, SFsdDevIoctlCompletion, NULL, TRUE, TRUE, TRUE); + // Send the request. + RC = IoCallDriver(PtrVCB->TargetDeviceObject, PtrIrp); + break; + } + + try_exit: NOTHING; + + } finally { + + // Release the IRP context + if (!(PtrIrpContext->IrpContextFlags & SFSD_IRP_CONTEXT_EXCEPTION)) { + // Free up the Irp Context + SFsdReleaseIrpContext(PtrIrpContext); + + if (CompleteIrp) { + PtrIrp->IoStatus.Status = RC; + PtrIrp->IoStatus.Information = 0; + + // complete the IRP + IoCompleteRequest(PtrIrp, IO_DISK_INCREMENT); + } + } + } + + return(RC); +} + + +/************************************************************************* +* +* Function: SFsdDevIoctlCompletion() +* +* Description: +* Completion routine. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS +* +*************************************************************************/ +NTSTATUS SFsdDevIoctlCompletion( +PDEVICE_OBJECT PtrDeviceObject, +PIRP PtrIrp, +void *Context) +{ + if (PtrIrp->PendingReturned) { + IoMarkIrpPending(PtrIrp); + } + + return(STATUS_SUCCESS); +} + + +/************************************************************************* +* +* Function: SFsdHandleQueryPath() +* +* Description: +* Handle the MUP request. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS +* +*************************************************************************/ +NTSTATUS SFsdHandleQueryPath( +void *BufferPointer) +{ + NTSTATUS RC = STATUS_SUCCESS; + PQUERY_PATH_REQUEST RequestBuffer = (PQUERY_PATH_REQUEST)BufferPointer; + PQUERY_PATH_RESPONSE ReplyBuffer = (PQUERY_PATH_RESPONSE)BufferPointer; + ULONG LengthOfNameToBeMatched = RequestBuffer->PathNameLength; + ULONG LengthOfMatchedName = 0; + WCHAR *NameToBeMatched = RequestBuffer->FilePathName; + + // So here we are. Simply check the name supplied. + // You can use whatever algorithm you like to determine whether the + // sent in name is acceptable. + // The first character in the name is always a "\" + // If you like the name sent in (probably, you will like a subset + // of the name), set the matching length value in LengthOfMatchedName. + + // if (FoundMatch) { + // ReplyBuffer->LengthAccepted = LengthOfMatchedName; + // } else { + // RC = STATUS_OBJECT_NAME_NOT_FOUND; + // } + + return(RC); +} + + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/src/dircntrl.c`: + +```c +/************************************************************************* +* +* File: dircntrl.c +* +* Module: Sample File System Driver (Kernel mode execution only) +* +* Description: +* Contains code to handle the "directory control" dispatch entry point. +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +#include "sfsd.h" + +// define the file specific bug-check id +#define SFSD_BUG_CHECK_ID SFSD_FILE_DIR_CONTROL + + + +/************************************************************************* +* +* Function: SFsdDirControl() +* +* Description: +* The I/O Manager will invoke this routine to handle a directory control +* request +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL (invocation at higher IRQL will cause execution +* to be deferred to a worker thread context) +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFsdDirControl( +PDEVICE_OBJECT DeviceObject, // the logical volume device object +PIRP Irp) // I/O Request Packet +{ + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdIrpContext PtrIrpContext = NULL; + BOOLEAN AreWeTopLevel = FALSE; + + FsRtlEnterFileSystem(); + ASSERT(DeviceObject); + ASSERT(Irp); + + // set the top level context + AreWeTopLevel = SFsdIsIrpTopLevel(Irp); + + try { + + // get an IRP context structure and issue the request + PtrIrpContext = SFsdAllocateIrpContext(Irp, DeviceObject); + ASSERT(PtrIrpContext); + + RC = SFsdCommonDirControl(PtrIrpContext, Irp); + + } except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) { + + RC = SFsdExceptionHandler(PtrIrpContext, Irp); + + SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC); + } + + if (AreWeTopLevel) { + IoSetTopLevelIrp(NULL); + } + + FsRtlExitFileSystem(); + + return(RC); +} + + + +/************************************************************************* +* +* Function: SFsdCommonDirControl() +* +* Description: +* The actual work is performed here. This routine may be invoked in one' +* of the two possible contexts: +* (a) in the context of a system worker thread +* (b) in the context of the original caller +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFsdCommonDirControl( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp) +{ + NTSTATUS RC = STATUS_SUCCESS; + PIO_STACK_LOCATION PtrIoStackLocation = NULL; + PFILE_OBJECT PtrFileObject = NULL; + PtrSFsdFCB PtrFCB = NULL; + PtrSFsdCCB PtrCCB = NULL; + PtrSFsdVCB PtrVCB = NULL; + + // First, get a pointer to the current I/O stack location + PtrIoStackLocation = IoGetCurrentIrpStackLocation(PtrIrp); + ASSERT(PtrIoStackLocation); + + PtrFileObject = PtrIoStackLocation->FileObject; + ASSERT(PtrFileObject); + + // Get the FCB and CCB pointers + PtrCCB = (PtrSFsdCCB)(PtrFileObject->FsContext2); + ASSERT(PtrCCB); + PtrFCB = PtrCCB->PtrFCB; + ASSERT(PtrFCB); + + // Get some of the parameters supplied to us + switch (PtrIoStackLocation->MinorFunction) { + case IRP_MN_QUERY_DIRECTORY: + RC = SFsdQueryDirectory(PtrIrpContext, PtrIrp, PtrIoStackLocation, PtrFileObject, PtrFCB, PtrCCB); + break; + case IRP_MN_NOTIFY_CHANGE_DIRECTORY: + RC = SFsdNotifyChangeDirectory(PtrIrpContext, PtrIrp, PtrIoStackLocation, PtrFileObject, PtrFCB, PtrCCB); + break; + default: + // This should not happen. + RC = STATUS_INVALID_DEVICE_REQUEST; + PtrIrp->IoStatus.Status = RC; + PtrIrp->IoStatus.Information = 0; + + // Free up the Irp Context + SFsdReleaseIrpContext(PtrIrpContext); + + // complete the IRP + IoCompleteRequest(PtrIrp, IO_NO_INCREMENT); + break; + } + + return(RC); +} + + +/************************************************************************* +* +* Function: SFsdQueryDirectory() +* +* Description: +* Query directory request. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFsdQueryDirectory( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp, +PIO_STACK_LOCATION PtrIoStackLocation, +PFILE_OBJECT PtrFileObject, +PtrSFsdFCB PtrFCB, +PtrSFsdCCB PtrCCB) +{ + NTSTATUS RC = STATUS_SUCCESS; + BOOLEAN CompleteRequest = TRUE; + BOOLEAN PostRequest = FALSE; + PtrSFsdNTRequiredFCB PtrReqdFCB = NULL; + BOOLEAN CanWait = FALSE; + PtrSFsdVCB PtrVCB = NULL; + BOOLEAN AcquiredFCB = FALSE; + unsigned long BufferLength = 0; + PSTRING PtrSearchPattern = NULL; + FILE_INFORMATION_CLASS FileInformationClass; + unsigned long FileIndex = 0; + BOOLEAN RestartScan = FALSE; + BOOLEAN ReturnSingleEntry = FALSE; + BOOLEAN IndexSpecified = FALSE; + unsigned char *Buffer = NULL; + BOOLEAN FirstTimeQuery = FALSE; + unsigned long StartingIndexForSearch = 0; + unsigned int BytesReturned = 0; + + try { + + // Validate the sent-in FCB + if ((PtrFCB->NodeIdentifier.NodeType == SFSD_NODE_TYPE_VCB) || !(PtrFCB->FCBFlags & SFSD_FCB_DIRECTORY)) { + // We will only allow notify requests on directories. + RC = STATUS_INVALID_PARAMETER; + } + + PtrReqdFCB = &(PtrFCB->NTRequiredFCB); + CanWait = ((PtrIrpContext->IrpContextFlags & SFSD_IRP_CONTEXT_CAN_BLOCK) ? TRUE : FALSE); + PtrVCB = PtrFCB->PtrVCB; + + // If the caller does not wish to block, it would be easier to + // simply post the request now. + if (!CanWait) { + PostRequest = TRUE; + try_return(RC = STATUS_PENDING); + } + + // Obtain the callers parameters + BufferLength = PtrIoStackLocation->Parameters.QueryDirectory.Length; + PtrSearchPattern = PtrIoStackLocation->Parameters.QueryDirectory.FileName; + FileInformationClass = PtrIoStackLocation->Parameters.QueryDirectory.FileInformationClass; + FileIndex = PtrIoStackLocation->Parameters.QueryDirectory.FileIndex; + + // Some additional arguments that affect the FSD behavior + RestartScan = (PtrIoStackLocation->Flags & SL_RESTART_SCAN); + ReturnSingleEntry = (PtrIoStackLocation->Flags & SL_RETURN_SINGLE_ENTRY); + IndexSpecified = (PtrIoStackLocation->Flags & SL_INDEX_SPECIFIED); + + // I will acquire exclusive access to the FCB. + // This is not mandatory, however, and your FSD could choose to acquire + // the resource shared for increased parallelism. + ExAcquireResourceExclusiveLite(&(PtrReqdFCB->MainResource), TRUE); + AcquiredFCB = TRUE; + + // We must determine the buffer pointer to be used. Since this + // routine could either be invoked directly in the context of the + // calling thread, or in the context of a worker thread, here is + // a general way of determining what we should use. + if (PtrIrp->MdlAddress) { + Buffer = MmGetSystemAddressForMdl(PtrIrp->MdlAddress); + } else { + Buffer = PtrIrp->UserBuffer; + } + + // The method of determining where to look from and what to look for is + // unfortunately extremely confusing. However, here is a methodology you + // you can broadly adopt: + // (a) You have to maintain a search buffer per CCB structure. + // (b) This search buffer is initialized the very first time + // a query directory operation is performed using the file object. + // (For the sample FSD, the search buffer is stored in the + // DirectorySearchPattern field) + // However, the caller still has the option of "overriding" this stored + // search pattern by supplying a new one in a query directory operation. + // + if (PtrSearchPattern == NULL) { + // User has supplied a search pattern + // Now validate that the search pattern is legitimate; this is + // dependent upon the character set acceptable to your FSD. + + // Once you have validated the search pattern, you must + // check whether you need to store this search pattern in + // the CCB. + if (PtrCCB->DirectorySearchPattern == NULL) { + // This must be the very first query request. + FirstTimeQuery = TRUE; + + // Now, allocate enough memory to contain the caller + // supplied search pattern and fill in the DirectorySearchPattern + // field in the CCB + // PtrCCB->DirectorySearchPattern = ExAllocatePool(...); + } else { + // We should ignore the search pattern in the CCB and instead, + // use the user-supplied pattern for this particular query + // directory request. + } + + } else if (PtrCCB->DirectorySearchPattern == NULL) { + // This MUST be the first directory query operation (else the + // DirectorySearchPattern field would never be NULL. Also, the caller + // has neglected to provide a pattern so we MUST invent one. + // Use "*" (following NT conventions) as your search pattern + // and store it in the PtrCCB->DirectorySearchPattern field. + + PtrCCB->DirectorySearchPattern = ExAllocatePool(PagedPool, sizeof(L"*")); + ASSERT(PtrCCB->DirectorySearchPattern); + + FirstTimeQuery = TRUE; + } else { + // The caller has not supplied any search pattern that we are + // forced to use. However, the caller had previously supplied + // a pattern (or we must have invented one) and we will use it. + // This is definitely not the first query operation on this + // directory using this particular file object. + + PtrSearchPattern = PtrCCB->DirectorySearchPattern; + } + + // There is one other piece of information that your FSD must store + // in the CCB structure for query directory support. This is the index + // value (i.e. the offset in your on-disk directory structure) from + // which you should start searching. + // However, the flags supplied with the IRP can make us override this + // as well. + + if (FileIndex) { + // Caller has told us wherefrom to begin. + // You may need to round this to an appropriate directory entry + // entry alignment value. + StartingIndexForSearch = FileIndex; + } else if (RestartScan) { + StartingIndexForSearch = 0; + } else { + // Get the starting offset from the CCB. + // Remember to update this value on your way out from this function. + // But, do not update the CCB CurrentByteOffset field if you reach + // the end of the directory (or get an error reading the directory) + // while performing the search. + StartingIndexForSearch = PtrCCB->CurrentByteOffset.LowPart; + } + + // Now, your FSD must determine the best way to read the directory + // contents from disk and search through them. + + // If ReturnSingleEntry is TRUE, please return information on only + // one matching entry. + + // One final note though: + // If you do not find a directory entry OR while searching you reach the + // end of the directory, then the return code should be set as follows: + + // (a) If any files have been returned (i.e. ReturnSingleEntry was FALSE + // and you did find at least one match), then return STATUS_SUCCESS + // (b) If no entry is being returned then: + // (i) If this is the first query i.e. FirstTimeQuery is TRUE + // then return STATUS_NO_SUCH_FILE + // (ii) Otherwise, return STATUS_NO_MORE_FILES + + try_exit: NOTHING; + + // Remember to update the CurrentByteOffset field in the CCB if required. + + // You should also set a flag in the FCB indicating that the directory + // contents were accessed. + + } finally { + if (PostRequest) { + if (AcquiredFCB) { + SFsdReleaseResource(&(PtrReqdFCB->MainResource)); + } + + // Map the users buffer and then post the request. + RC = SFsdLockCallersBuffer(PtrIrp, TRUE, BufferLength); + ASSERT(NT_SUCCESS(RC)); + + RC = SFsdPostRequest(PtrIrpContext, PtrIrp); + + } else if (!(PtrIrpContext->IrpContextFlags & + SFSD_IRP_CONTEXT_EXCEPTION)) { + if (AcquiredFCB) { + SFsdReleaseResource(&(PtrReqdFCB->MainResource)); + } + + // Complete the request. + PtrIrp->IoStatus.Status = RC; + PtrIrp->IoStatus.Information = BytesReturned; + + // Free up the Irp Context + SFsdReleaseIrpContext(PtrIrpContext); + + // complete the IRP + IoCompleteRequest(PtrIrp, IO_DISK_INCREMENT); + } + } + + return(RC); +} + + + +/************************************************************************* +* +* Function: SFsdNotifyChangeDirectory() +* +* Description: +* Handle the notify request. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFsdNotifyChangeDirectory( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp, +PIO_STACK_LOCATION PtrIoStackLocation, +PFILE_OBJECT PtrFileObject, +PtrSFsdFCB PtrFCB, +PtrSFsdCCB PtrCCB) +{ + NTSTATUS RC = STATUS_SUCCESS; + BOOLEAN CompleteRequest = FALSE; + BOOLEAN PostRequest = FALSE; + PtrSFsdNTRequiredFCB PtrReqdFCB = NULL; + BOOLEAN CanWait = FALSE; + ULONG CompletionFilter = 0; + BOOLEAN WatchTree = FALSE; + PtrSFsdVCB PtrVCB = NULL; + BOOLEAN AcquiredFCB = FALSE; + + try { + + // Validate the sent-in FCB + if ((PtrFCB->NodeIdentifier.NodeType == SFSD_NODE_TYPE_VCB) || !(PtrFCB->FCBFlags & SFSD_FCB_DIRECTORY)) { + // We will only allow notify requests on directories. + RC = STATUS_INVALID_PARAMETER; + CompleteRequest = TRUE; + } + + PtrReqdFCB = &(PtrFCB->NTRequiredFCB); + CanWait = ((PtrIrpContext->IrpContextFlags & SFSD_IRP_CONTEXT_CAN_BLOCK) ? TRUE : FALSE); + PtrVCB = PtrFCB->PtrVCB; + + // Acquire the FCB resource shared + if (!ExAcquireResourceSharedLite(&(PtrReqdFCB->MainResource), CanWait)) { + PostRequest = TRUE; + try_return(RC = STATUS_PENDING); + } + AcquiredFCB = TRUE; + + // Obtain some parameters sent by the caller + CompletionFilter = PtrIoStackLocation->Parameters.NotifyDirectory.CompletionFilter; + WatchTree = (PtrIoStackLocation->Flags & SL_WATCH_TREE ? TRUE : FALSE); + + // If you wish to capture the subject context, you can do so as + // follows: + // { + // PSECURITY_SUBJECT_CONTEXT SubjectContext; + // SubjectContext = ExAllocatePool(PagedPool, + // sizeof(SECURITY_SUBJECT_CONTEXT)); + // SeCaptureSubjectContext(SubjectContext); + // } + + FsRtlNotifyFullChangeDirectory(&(PtrVCB->NotifyIRPMutex), &(PtrVCB->NextNotifyIRP), (void *)PtrCCB, + (PSTRING)(PtrFCB->FCBName->ObjectName.Buffer), WatchTree, FALSE, CompletionFilter, PtrIrp, + NULL, // SFsdTraverseAccessCheck(...) ? + NULL); // SubjectContext ? + + RC = STATUS_PENDING; + + try_exit: NOTHING; + + } finally { + + if (PostRequest) { + // Perform appropriate post related processing here + if (AcquiredFCB) { + SFsdReleaseResource(&(PtrReqdFCB->MainResource)); + AcquiredFCB = FALSE; + } + RC = SFsdPostRequest(PtrIrpContext, PtrIrp); + } else if (CompleteRequest) { + PtrIrp->IoStatus.Status = RC; + PtrIrp->IoStatus.Information = 0; + + // Free up the Irp Context + SFsdReleaseIrpContext(PtrIrpContext); + + // complete the IRP + IoCompleteRequest(PtrIrp, IO_DISK_INCREMENT); + } else { + // Simply free up the IrpContext since the IRP has been queued + SFsdReleaseIrpContext(PtrIrpContext); + } + + // Release the FCB resources if acquired. + if (AcquiredFCB) { + SFsdReleaseResource(&(PtrReqdFCB->MainResource)); + AcquiredFCB = FALSE; + } + + } + + return(RC); +} + + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/src/fastio.c`: + +```c +/************************************************************************* +* +* File: fastio.c +* +* Module: Sample File System Driver (Kernel mode execution only) +* +* Description: +* Contains code to handle the various "fast-io" calls. +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +#include "sfsd.h" + +// define the file specific bug-check id +#define SFSD_BUG_CHECK_ID SFSD_FILE_FAST_IO + + + +/************************************************************************* +* +* Function: SFsdFastIoCheckIfPossible() +* +* Description: +* To fast-io or not to fast-io, that is the question ... +* This routine helps the I/O Manager determine whether the FSD wishes +* to permit fast-io on a specific file stream. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFsdFastIoCheckIfPossible( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN ULONG Length, +IN BOOLEAN Wait, +IN ULONG LockKey, +IN BOOLEAN CheckForReadOperation, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN ReturnedStatus = FALSE; + PtrSFsdFCB PtrFCB = NULL; + PtrSFsdCCB PtrCCB = NULL; + LARGE_INTEGER IoLength; + + // Obtain a pointer to the FCB and CCB for the file stream. + PtrCCB = (PtrSFsdCCB)(FileObject->FsContext2); + ASSERT(PtrCCB); + PtrFCB = PtrCCB->PtrFCB; + ASSERT(PtrFCB); + + // Validate that this is a fast-IO request to a regular file. + // The sample FSD for example, will not allow fast-IO requests + // to volume objects, or to directories. + if ((PtrFCB->NodeIdentifier.NodeType == SFSD_NODE_TYPE_VCB) || + (PtrFCB->FCBFlags & SFSD_FCB_DIRECTORY)) { + // This is not allowed. + return(ReturnedStatus); + } + + IoLength = RtlConvertUlongToLargeInteger(Length); + + // Your FSD can determine the checks that it needs to perform. + // Typically, a FSD will check whether there exist any byte-range + // locks that would prevent a fast-IO operation from proceeding. + + // ... (FSD specific checks go here). + + if (CheckForReadOperation) { + // Chapter 11 describes how to use the FSRTL package for byte-range + // lock requests. The following routine is exported by the FSRTL + // package and it returns TRUE if the read operation should be + // allowed to proceed based on the status of the current byte-range + // locks on the file stream. If you do not use the FSRTL package + // for byte-range locking support, then you must substitute your + // own checks over here. + // ReturnedStatus = FsRtlFastCheckLockForRead(&(PtrFCB->FCBByteRangeLock), + // FileOffset, &IoLength, LockKey, FileObject, + // PsGetCurrentProcess()); + } else { + // This is a write request. Invoke the FSRTL byte-range lock package + // to see whether the write should be allowed to proceed. + // ReturnedStatus = FsRtlFastCheckLockForWrite(&(PtrFCB->FCBByteRangeLock), + // FileOffset, &IoLength, LockKey, FileObject, + // PsGetCurrentProcess()); + } + + return(ReturnedStatus); +} + + +/************************************************************************* +* +* Function: SFsdFastIoRead() +* +* Description: +* Bypass the traditional IRP method to perform a read operation. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFsdFastIoRead( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN ULONG Length, +IN BOOLEAN Wait, +IN ULONG LockKey, +OUT PVOID Buffer, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN ReturnedStatus = FALSE; // fast i/o failed/not allowed + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdIrpContext PtrIrpContext = NULL; + + FsRtlEnterFileSystem(); + + try { + + try { + + // Chapter 11 describes how to roll your own fast-IO entry points. + // Typically, you will acquire appropriate resources here and + // then (maybe) forward the request to FsRtlCopyRead(). + // If you are a suitably complex file system, you may even choose + // to do some pre-processing (e.g. prefetching data from someplace) + // before passing on the request to the FSRTL package. + + // Of course, you also have the option of bypassing the FSRTL + // package completely and simply forwarding the request directly + // to the NT Cache Manager. + + // Bottom line is that you have complete flexibility on determining + // what you decide to do here. Read Chapter 11 well (and obviously + // other related issues) before filling in this and other fast-IO + // dispatch entry points. + + NOTHING; + + } except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) { + + RC = SFsdExceptionHandler(PtrIrpContext, NULL); + + SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC); + + } + + try_exit: NOTHING; + + } finally { + + } + + FsRtlExitFileSystem(); + + return(ReturnedStatus); +} + + +/************************************************************************* +* +* Function: SFsdFastIoWrite() +* +* Description: +* Bypass the traditional IRP method to perform a write operation. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFsdFastIoWrite( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN ULONG Length, +IN BOOLEAN Wait, +IN ULONG LockKey, +OUT PVOID Buffer, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN ReturnedStatus = FALSE; // fast i/o failed/not allowed + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdIrpContext PtrIrpContext = NULL; + + FsRtlEnterFileSystem(); + + try { + + try { + + // See description in SFsdFastIoRead() before filling-in the + // stub here. + NOTHING; + + } except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) { + + RC = SFsdExceptionHandler(PtrIrpContext, NULL); + + SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC); + + } + + try_exit: NOTHING; + + } finally { + + } + + FsRtlExitFileSystem(); + + return(ReturnedStatus); +} + + +/************************************************************************* +* +* Function: SFsdFastIoQueryBasicInfo() +* +* Description: +* Bypass the traditional IRP method to perform a query basic +* information operation. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFsdFastIoQueryBasicInfo( +IN PFILE_OBJECT FileObject, +IN BOOLEAN Wait, +OUT PFILE_BASIC_INFORMATION Buffer, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN ReturnedStatus = FALSE; // fast i/o failed/not allowed + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdIrpContext PtrIrpContext = NULL; + + FsRtlEnterFileSystem(); + + try { + + try { + + // See description in SFsdFastIoRead() before filling-in the + // stub here. + NOTHING; + + } except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) { + + RC = SFsdExceptionHandler(PtrIrpContext, NULL); + + SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC); + + } + + try_exit: NOTHING; + + } finally { + + } + + FsRtlExitFileSystem(); + + return(ReturnedStatus); +} + + +/************************************************************************* +* +* Function: SFsdFastIoQueryStdInfo() +* +* Description: +* Bypass the traditional IRP method to perform a query standard +* information operation. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFsdFastIoQueryStdInfo( +IN PFILE_OBJECT FileObject, +IN BOOLEAN Wait, +OUT PFILE_STANDARD_INFORMATION Buffer, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN ReturnedStatus = FALSE; // fast i/o failed/not allowed + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdIrpContext PtrIrpContext = NULL; + + FsRtlEnterFileSystem(); + + try { + + try { + + // See description in SFsdFastIoRead() before filling-in the + // stub here. + NOTHING; + + } except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) { + + RC = SFsdExceptionHandler(PtrIrpContext, NULL); + + SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC); + + } + + try_exit: NOTHING; + + } finally { + + } + + FsRtlExitFileSystem(); + + return(ReturnedStatus); +} + + +/************************************************************************* +* +* Function: SFsdFastIoLock() +* +* Description: +* Bypass the traditional IRP method to perform a byte range lock +* operation. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFsdFastIoLock( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN PLARGE_INTEGER Length, +PEPROCESS ProcessId, +ULONG Key, +BOOLEAN FailImmediately, +BOOLEAN ExclusiveLock, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN ReturnedStatus = FALSE; // fast i/o failed/not allowed + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdIrpContext PtrIrpContext = NULL; + + FsRtlEnterFileSystem(); + + try { + + try { + + // See description in SFsdFastIoRead() before filling-in the + // stub here. + NOTHING; + + } except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) { + + RC = SFsdExceptionHandler(PtrIrpContext, NULL); + + SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC); + + } + + try_exit: NOTHING; + + } finally { + + } + + FsRtlExitFileSystem(); + + return(ReturnedStatus); +} + + +/************************************************************************* +* +* Function: SFsdFastIoUnlockSingle() +* +* Description: +* Bypass the traditional IRP method to perform a byte range unlock +* operation. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFsdFastIoUnlockSingle( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN PLARGE_INTEGER Length, +PEPROCESS ProcessId, +ULONG Key, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN ReturnedStatus = FALSE; // fast i/o failed/not allowed + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdIrpContext PtrIrpContext = NULL; + + FsRtlEnterFileSystem(); + + try { + + try { + + // See description in SFsdFastIoRead() before filling-in the + // stub here. + NOTHING; + + } except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) { + + RC = SFsdExceptionHandler(PtrIrpContext, NULL); + + SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC); + + } + + try_exit: NOTHING; + + } finally { + + } + + FsRtlExitFileSystem(); + + return(ReturnedStatus); +} + + +/************************************************************************* +* +* Function: SFsdFastIoUnlockAll() +* +* Description: +* Bypass the traditional IRP method to perform multiple byte range unlock +* operations. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFsdFastIoUnlockAll( +IN PFILE_OBJECT FileObject, +PEPROCESS ProcessId, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN ReturnedStatus = FALSE; // fast i/o failed/not allowed + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdIrpContext PtrIrpContext = NULL; + + FsRtlEnterFileSystem(); + + try { + + try { + + // See description in SFsdFastIoRead() before filling-in the + // stub here. + NOTHING; + + } except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) { + + RC = SFsdExceptionHandler(PtrIrpContext, NULL); + + SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC); + + } + + try_exit: NOTHING; + + } finally { + + } + + FsRtlExitFileSystem(); + + return(ReturnedStatus); +} + + +/************************************************************************* +* +* Function: SFsdFastIoUnlockAllByKey() +* +* Description: +* Bypass the traditional IRP method to perform multiple byte range unlock +* operations. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFsdFastIoUnlockAllByKey( +IN PFILE_OBJECT FileObject, +PEPROCESS ProcessId, +ULONG Key, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN ReturnedStatus = FALSE; // fast i/o failed/not allowed + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdIrpContext PtrIrpContext = NULL; + + FsRtlEnterFileSystem(); + + try { + + try { + + // See description in SFsdFastIoRead() before filling-in the + // stub here. + NOTHING; + + } except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) { + + RC = SFsdExceptionHandler(PtrIrpContext, NULL); + + SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC); + + } + + try_exit: NOTHING; + + } finally { + + } + + FsRtlExitFileSystem(); + + return(ReturnedStatus); +} + + +/************************************************************************* +* +* Function: SFsdFastIoAcqCreateSec() +* +* Description: +* Not really a fast-io operation. Used by the VMM to acquire FSD resources +* before processing a file map (create section object) request. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: None (we must be prepared to handle VMM initiated calls) +* +*************************************************************************/ +void SFsdFastIoAcqCreateSec( +IN PFILE_OBJECT FileObject) +{ + PtrSFsdFCB PtrFCB = NULL; + PtrSFsdCCB PtrCCB = NULL; + PtrSFsdNTRequiredFCB PtrReqdFCB = NULL; + + // Obtain a pointer to the FCB and CCB for the file stream. + PtrCCB = (PtrSFsdCCB)(FileObject->FsContext2); + ASSERT(PtrCCB); + PtrFCB = PtrCCB->PtrFCB; + ASSERT(PtrFCB); + PtrReqdFCB = &(PtrFCB->NTRequiredFCB); + + // Acquire the MainResource exclusively for the file stream + ExAcquireResourceExclusiveLite(&(PtrReqdFCB->MainResource), TRUE); + + // Although this is typically not required, the sample FSD will + // also acquire the PagingIoResource exclusively at this time + // to conform with the resource acquisition described in the set + // file information routine. Once again though, you will probably + // not need to do this. + ExAcquireResourceExclusiveLite(&(PtrReqdFCB->PagingIoResource), TRUE); + + return; +} + + +/************************************************************************* +* +* Function: SFsdFastIoRelCreateSec() +* +* Description: +* Not really a fast-io operation. Used by the VMM to release FSD resources +* after processing a file map (create section object) request. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: None +* +*************************************************************************/ +void SFsdFastIoRelCreateSec( +IN PFILE_OBJECT FileObject) +{ + + PtrSFsdFCB PtrFCB = NULL; + PtrSFsdCCB PtrCCB = NULL; + PtrSFsdNTRequiredFCB PtrReqdFCB = NULL; + + // Obtain a pointer to the FCB and CCB for the file stream. + PtrCCB = (PtrSFsdCCB)(FileObject->FsContext2); + ASSERT(PtrCCB); + PtrFCB = PtrCCB->PtrFCB; + ASSERT(PtrFCB); + PtrReqdFCB = &(PtrFCB->NTRequiredFCB); + + // Release the PagingIoResource for the file stream + SFsdReleaseResource(&(PtrReqdFCB->PagingIoResource)); + + // Release the MainResource for the file stream + SFsdReleaseResource(&(PtrReqdFCB->MainResource)); + + return; +} + + +/************************************************************************* +* +* Function: SFsdAcqLazyWrite() +* +* Description: +* Not really a fast-io operation. Used by the NT Cache Mgr to acquire FSD +* resources before performing a delayed write (write behind/lazy write) +* operation. +* NOTE: this function really must succeed since the Cache Manager will +* typically ignore failure and continue on ... +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE (Cache Manager does not tolerate FALSE well) +* +*************************************************************************/ +BOOLEAN SFsdAcqLazyWrite( +IN PVOID Context, +IN BOOLEAN Wait) +{ + BOOLEAN ReturnedStatus = TRUE; + + PtrSFsdFCB PtrFCB = NULL; + PtrSFsdCCB PtrCCB = NULL; + PtrSFsdNTRequiredFCB PtrReqdFCB = NULL; + + // The context is whatever we passed to the Cache Manager when invoking + // the CcInitializeCacheMaps() function. In the case of the sample FSD + // implementation, this context is a pointer to the CCB structure. + + ASSERT(Context); + PtrCCB = (PtrSFsdCCB)(Context); + ASSERT(PtrCCB->NodeIdentifier.NodeType == SFSD_NODE_TYPE_CCB); + + PtrFCB = PtrCCB->PtrFCB; + ASSERT(PtrFCB); + PtrReqdFCB = &(PtrFCB->NTRequiredFCB); + + // Acquire the MainResource in the FCB exclusively. Then, set the + // lazy-writer thread id in the FCB structure for identification when + // an actual write request is received by the FSD. + // Note: The lazy-writer typically always supplies WAIT set to TRUE. + if (!ExAcquireResourceExclusiveLite(&(PtrReqdFCB->MainResource), + Wait)) { + ReturnedStatus = FALSE; + } else { + // Now, set the lazy-writer thread id. + ASSERT(!(PtrFCB->LazyWriterThreadID)); + PtrFCB->LazyWriterThreadID = (unsigned int)(PsGetCurrentThread()); + } + + // If your FSD needs to perform some special preparations in anticipation + // of receving a lazy-writer request, do so now. + + return(ReturnedStatus); +} + + +/************************************************************************* +* +* Function: SFsdRelLazyWrite() +* +* Description: +* Not really a fast-io operation. Used by the NT Cache Mgr to release FSD +* resources after performing a delayed write (write behind/lazy write) +* operation. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: None +* +*************************************************************************/ +void SFsdRelLazyWrite( +IN PVOID Context) +{ + BOOLEAN ReturnedStatus = TRUE; + + PtrSFsdFCB PtrFCB = NULL; + PtrSFsdCCB PtrCCB = NULL; + PtrSFsdNTRequiredFCB PtrReqdFCB = NULL; + + // The context is whatever we passed to the Cache Manager when invoking + // the CcInitializeCacheMaps() function. In the case of the sample FSD + // implementation, this context is a pointer to the CCB structure. + + ASSERT(Context); + PtrCCB = (PtrSFsdCCB)(Context); + ASSERT(PtrCCB->NodeIdentifier.NodeType == SFSD_NODE_TYPE_CCB); + + PtrFCB = PtrCCB->PtrFCB; + ASSERT(PtrFCB); + PtrReqdFCB = &(PtrFCB->NTRequiredFCB); + + // Remove the current thread-id from the FCB and release the MainResource. + ASSERT((PtrFCB->LazyWriterThreadID) == (unsigned int)PsGetCurrentThread()); + PtrFCB->LazyWriterThreadID = 0; + + // Release the acquired resource. + SFsdReleaseResource(&(PtrReqdFCB->MainResource)); + + // Of course, your FSD should undo whatever else seems appropriate at this + // time. + + return; +} + + +/************************************************************************* +* +* Function: SFsdAcqReadAhead() +* +* Description: +* Not really a fast-io operation. Used by the NT Cache Mgr to acquire FSD +* resources before performing a read-ahead operation. +* NOTE: this function really must succeed since the Cache Manager will +* typically ignore failure and continue on ... +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE (Cache Manager does not tolerate FALSE well) +* +*************************************************************************/ +BOOLEAN SFsdAcqReadAhead( +IN PVOID Context, +IN BOOLEAN Wait) +{ + + BOOLEAN ReturnedStatus = TRUE; + + PtrSFsdFCB PtrFCB = NULL; + PtrSFsdCCB PtrCCB = NULL; + PtrSFsdNTRequiredFCB PtrReqdFCB = NULL; + + // The context is whatever we passed to the Cache Manager when invoking + // the CcInitializeCacheMaps() function. In the case of the sample FSD + // implementation, this context is a pointer to the CCB structure. + + ASSERT(Context); + PtrCCB = (PtrSFsdCCB)(Context); + ASSERT(PtrCCB->NodeIdentifier.NodeType == SFSD_NODE_TYPE_CCB); + + PtrFCB = PtrCCB->PtrFCB; + ASSERT(PtrFCB); + PtrReqdFCB = &(PtrFCB->NTRequiredFCB); + + // Acquire the MainResource in the FCB shared. + // Note: The read-ahead thread typically always supplies WAIT set to TRUE. + if (!ExAcquireResourceSharedLite(&(PtrReqdFCB->MainResource), + Wait)) { + ReturnedStatus = FALSE; + } + + // If your FSD needs to perform some special preparations in anticipation + // of receving a read-ahead request, do so now. + + return(ReturnedStatus); +} + + + +/************************************************************************* +* +* Function: SFsdRelReadAhead() +* +* Description: +* Not really a fast-io operation. Used by the NT Cache Mgr to release FSD +* resources after performing a read-ahead operation. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: None +* +*************************************************************************/ +void SFsdRelReadAhead( +IN PVOID Context) +{ + BOOLEAN ReturnedStatus = TRUE; + + PtrSFsdFCB PtrFCB = NULL; + PtrSFsdCCB PtrCCB = NULL; + PtrSFsdNTRequiredFCB PtrReqdFCB = NULL; + + // The context is whatever we passed to the Cache Manager when invoking + // the CcInitializeCacheMaps() function. In the case of the sample FSD + // implementation, this context is a pointer to the CCB structure. + + ASSERT(Context); + PtrCCB = (PtrSFsdCCB)(Context); + ASSERT(PtrCCB->NodeIdentifier.NodeType == SFSD_NODE_TYPE_CCB); + + PtrFCB = PtrCCB->PtrFCB; + ASSERT(PtrFCB); + PtrReqdFCB = &(PtrFCB->NTRequiredFCB); + + // Release the acquired resource. + SFsdReleaseResource(&(PtrReqdFCB->MainResource)); + + // Of course, your FSD should undo whatever else seems appropriate at this + // time. + + return; +} + + +/* the remaining are only valid under NT Version 4.0 and later */ +#if(_WIN32_WINNT >= 0x0400) + + +/************************************************************************* +* +* Function: SFsdFastIoQueryNetInfo() +* +* Description: +* Get information requested by a redirector across the network. This call +* will originate from the LAN Manager server. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFsdFastIoQueryNetInfo( +IN PFILE_OBJECT FileObject, +IN BOOLEAN Wait, +OUT PFILE_NETWORK_OPEN_INFORMATION Buffer, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN ReturnedStatus = FALSE; // fast i/o failed/not allowed + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdIrpContext PtrIrpContext = NULL; + + FsRtlEnterFileSystem(); + + try { + + try { + + // See description in SFsdFastIoRead() before filling-in the + // stub here. + NOTHING; + + + } except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) { + + RC = SFsdExceptionHandler(PtrIrpContext, NULL); + + SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC); + + } + + try_exit: NOTHING; + + } finally { + + } + + FsRtlExitFileSystem(); + + return(ReturnedStatus); +} + + +/************************************************************************* +* +* Function: SFsdFastIoMdlRead() +* +* Description: +* Bypass the traditional IRP method to perform a MDL read operation. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFsdFastIoMdlRead( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN ULONG Length, +IN ULONG LockKey, +OUT PMDL *MdlChain, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN ReturnedStatus = FALSE; // fast i/o failed/not allowed + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdIrpContext PtrIrpContext = NULL; + + FsRtlEnterFileSystem(); + + try { + + try { + + // See description in SFsdFastIoRead() before filling-in the + // stub here. + NOTHING; + + + } except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) { + + RC = SFsdExceptionHandler(PtrIrpContext, NULL); + + SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC); + + } + + try_exit: NOTHING; + + } finally { + + } + + FsRtlExitFileSystem(); + + return(ReturnedStatus); +} + + +/************************************************************************* +* +* Function: SFsdFastIoMdlReadComplete() +* +* Description: +* Bypass the traditional IRP method to inform the NT Cache Manager and the +* FSD that the caller no longer requires the data locked in the system cache +* or the MDL to stay around anymore .. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFsdFastIoMdlReadComplete( +IN PFILE_OBJECT FileObject, +OUT PMDL MdlChain, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN ReturnedStatus = FALSE; // fast i/o failed/not allowed + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdIrpContext PtrIrpContext = NULL; + + FsRtlEnterFileSystem(); + + try { + + try { + + // See description in SFsdFastIoRead() before filling-in the + // stub here. + NOTHING; + + } except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) { + + RC = SFsdExceptionHandler(PtrIrpContext, NULL); + + SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC); + + } + + try_exit: NOTHING; + + } finally { + + } + + FsRtlExitFileSystem(); + + return(ReturnedStatus); +} + + +/************************************************************************* +* +* Function: SFsdFastIoPrepareMdlWrite() +* +* Description: +* Bypass the traditional IRP method to prepare for a MDL write operation. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFsdFastIoPrepareMdlWrite( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN ULONG Length, +IN ULONG LockKey, +OUT PMDL *MdlChain, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN ReturnedStatus = FALSE; // fast i/o failed/not allowed + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdIrpContext PtrIrpContext = NULL; + + FsRtlEnterFileSystem(); + + try { + + try { + + // See description in SFsdFastIoRead() before filling-in the + // stub here. + NOTHING; + + } except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) { + + RC = SFsdExceptionHandler(PtrIrpContext, NULL); + + SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC); + + } + + try_exit: NOTHING; + + } finally { + + } + + FsRtlExitFileSystem(); + + return(ReturnedStatus); +} + + +/************************************************************************* +* +* Function: SFsdFastIoMdlWriteComplete() +* +* Description: +* Bypass the traditional IRP method to inform the NT Cache Manager and the +* FSD that the caller has updated the contents of the MDL. This data can +* now be asynchronously written out to secondary storage by the Cache Mgr. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFsdFastIoMdlWriteComplete( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +OUT PMDL MdlChain, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN ReturnedStatus = FALSE; // fast i/o failed/not allowed + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdIrpContext PtrIrpContext = NULL; + + FsRtlEnterFileSystem(); + + try { + + try { + + // See description in SFsdFastIoRead() before filling-in the + // stub here. + NOTHING; + + } except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) { + + RC = SFsdExceptionHandler(PtrIrpContext, NULL); + + SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC); + + } + + try_exit: NOTHING; + + } finally { + + } + + FsRtlExitFileSystem(); + + return(ReturnedStatus); +} + + +/************************************************************************* +* +* Function: SFsdFastIoAcqModWrite() +* +* Description: +* Not really a fast-io operation. Used by the VMM to acquire FSD resources +* before initiating a write operation via the Modified Page/Block Writer. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error (try not to return an error, will 'ya ? :-) +* +*************************************************************************/ +NTSTATUS SFsdFastIoAcqModWrite( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER EndingOffset, +OUT PERESOURCE *ResourceToRelease, +IN PDEVICE_OBJECT DeviceObject) +{ + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdIrpContext PtrIrpContext = NULL; + + FsRtlEnterFileSystem(); + + try { + + try { + + // You must determine which resource(s) you would like to + // acquire at this time. You know that a write is imminent; + // you will probably therefore acquire appropriate resources + // exclusively. + + // You must first get the FCB and CCB pointers from the file object + // that is passed in to this function (as an argument). Note that + // the ending offset (when examined in conjunction with current valid data + // length) may help you in determining the appropriate resource(s) to acquire. + + // For example, if the ending offset is beyond current valid data length, + // you may decide to acquire *both* the MainResource and the PagingIoResource + // exclusively; otherwise, you may decide simply to acquire the PagingIoResource. + + // Consult the text for more information on synchronization in FSDs. + + // One final note; the VMM expects that you will return a pointer to + // the resource that you acquired (single return value). This pointer + // will be returned back to you in the release call (below). + + NOTHING; + + } except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) { + + RC = SFsdExceptionHandler(PtrIrpContext, NULL); + + SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC); + + } + + try_exit: NOTHING; + + } finally { + + } + + FsRtlExitFileSystem(); + + return(RC); +} + + +/************************************************************************* +* +* Function: SFsdFastIoRelModWrite() +* +* Description: +* Not really a fast-io operation. Used by the VMM to release FSD resources +* after processing a modified page/block write operation. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error (an error returned here is really not expected!) +* +*************************************************************************/ +NTSTATUS SFsdFastIoRelModWrite( +IN PFILE_OBJECT FileObject, +IN PERESOURCE ResourceToRelease, +IN PDEVICE_OBJECT DeviceObject) +{ + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdIrpContext PtrIrpContext = NULL; + + FsRtlEnterFileSystem(); + + try { + + try { + + // The MPW has complete the write for modified pages and therefore + // wants you to release pre-acquired resource(s). + + // You must undo here whatever it is that you did in the + // SFsdFastIoAcqModWrite() call above. + + NOTHING; + + } except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) { + + RC = SFsdExceptionHandler(PtrIrpContext, NULL); + + SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC); + + } + + try_exit: NOTHING; + + } finally { + + } + + FsRtlExitFileSystem(); + + return(RC); +} + + +/************************************************************************* +* +* Function: SFsdFastIoAcqCcFlush() +* +* Description: +* Not really a fast-io operation. Used by the NT Cache Mgr to acquire FSD +* resources before performing a CcFlush() operation on a specific file +* stream. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFsdFastIoAcqCcFlush( +IN PFILE_OBJECT FileObject, +IN PDEVICE_OBJECT DeviceObject) +{ + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdIrpContext PtrIrpContext = NULL; + + FsRtlEnterFileSystem(); + + try { + + try { + + // Acquire appropriate resources that will allow correct synchronization + // with a flush call (and avoid deadlock). + NOTHING; + + } except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) { + + RC = SFsdExceptionHandler(PtrIrpContext, NULL); + + SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC); + + } + + try_exit: NOTHING; + + } finally { + + } + + FsRtlExitFileSystem(); + + return(RC); +} + + +/************************************************************************* +* +* Function: SFsdFastIoRelCcFlush() +* +* Description: +* Not really a fast-io operation. Used by the NT Cache Mgr to acquire FSD +* resources before performing a CcFlush() operation on a specific file +* stream. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFsdFastIoRelCcFlush( +IN PFILE_OBJECT FileObject, +IN PDEVICE_OBJECT DeviceObject) +{ + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdIrpContext PtrIrpContext = NULL; + + FsRtlEnterFileSystem(); + + try { + + try { + + // Release resources acquired in SFsdFastIoAcqCcFlush() above. + NOTHING; + + } except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) { + + RC = SFsdExceptionHandler(PtrIrpContext, NULL); + + SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC); + + } + + try_exit: NOTHING; + + } finally { + + } + + FsRtlExitFileSystem(); + + return(RC); +} + +#endif //_WIN32_WINNT >= 0x0400 + + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/src/fileinfo.c`: + +```c +/************************************************************************* +* +* File: fileinfo.c +* +* Module: Sample File System Driver (Kernel mode execution only) +* +* Description: +* Contains code to handle the "set/query file information" dispatch +* entry points. +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +#include "sfsd.h" + +// define the file specific bug-check id +#define SFSD_BUG_CHECK_ID SFSD_FILE_INFORMATION + + + +/************************************************************************* +* +* Function: SFsdFileInfo() +* +* Description: +* The I/O Manager will invoke this routine to handle a set/query file +* information request +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL (invocation at higher IRQL will cause execution +* to be deferred to a worker thread context) +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFsdFileInfo( +PDEVICE_OBJECT DeviceObject, // the logical volume device object +PIRP Irp) // I/O Request Packet +{ + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdIrpContext PtrIrpContext = NULL; + BOOLEAN AreWeTopLevel = FALSE; + + FsRtlEnterFileSystem(); + ASSERT(DeviceObject); + ASSERT(Irp); + + // set the top level context + AreWeTopLevel = SFsdIsIrpTopLevel(Irp); + + try { + + // get an IRP context structure and issue the request + PtrIrpContext = SFsdAllocateIrpContext(Irp, DeviceObject); + ASSERT(PtrIrpContext); + + RC = SFsdCommonFileInfo(PtrIrpContext, Irp); + + } except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) { + + RC = SFsdExceptionHandler(PtrIrpContext, Irp); + + SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC); + } + + if (AreWeTopLevel) { + IoSetTopLevelIrp(NULL); + } + + FsRtlExitFileSystem(); + + return(RC); +} + + + +/************************************************************************* +* +* Function: SFsdCommonFileInfo() +* +* Description: +* The actual work is performed here. This routine may be invoked in one' +* of the two possible contexts: +* (a) in the context of a system worker thread +* (b) in the context of the original caller +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFsdCommonFileInfo( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp) +{ + NTSTATUS RC = STATUS_SUCCESS; + PIO_STACK_LOCATION PtrIoStackLocation = NULL; + PFILE_OBJECT PtrFileObject = NULL; + PtrSFsdFCB PtrFCB = NULL; + PtrSFsdCCB PtrCCB = NULL; + PtrSFsdVCB PtrVCB = NULL; + PtrSFsdNTRequiredFCB PtrReqdFCB = NULL; + BOOLEAN MainResourceAcquired = FALSE; + BOOLEAN VCBResourceAcquired = FALSE; + BOOLEAN PagingIoResourceAcquired = FALSE; + IO_STATUS_BLOCK LocalIoStatus; + void *PtrSystemBuffer = NULL; + long BufferLength = 0; + FILE_INFORMATION_CLASS FunctionalityRequested; + BOOLEAN CanWait = FALSE; + BOOLEAN PostRequest = FALSE; + + try { + // First, get a pointer to the current I/O stack location. + PtrIoStackLocation = IoGetCurrentIrpStackLocation(PtrIrp); + ASSERT(PtrIoStackLocation); + + PtrFileObject = PtrIoStackLocation->FileObject; + ASSERT(PtrFileObject); + + // Get the FCB and CCB pointers. + PtrCCB = (PtrSFsdCCB)(PtrFileObject->FsContext2); + ASSERT(PtrCCB); + PtrFCB = PtrCCB->PtrFCB; + ASSERT(PtrFCB); + + PtrReqdFCB = &(PtrFCB->NTRequiredFCB); + + CanWait = ((PtrIrpContext->IrpContextFlags & SFSD_IRP_CONTEXT_CAN_BLOCK) ? TRUE : FALSE); + + // If the caller has opened a logical volume and is attempting to + // query information for it as a file stream, return an error. + if (PtrFCB->NodeIdentifier.NodeType == SFSD_NODE_TYPE_VCB) { + // This is not allowed. Caller must use get/set volume information instead. + RC = STATUS_INVALID_PARAMETER; + try_return(RC); + } + + ASSERT(PtrFCB->NodeIdentifier.NodeType == SFSD_NODE_TYPE_FCB); + + // The NT I/O Manager always allocates and supplies a system + // buffer for query and set file information calls. + // Copying information to/from the user buffer and the system + // buffer is performed by the I/O Manager and the FSD need not worry about it. + PtrSystemBuffer = PtrIrp->AssociatedIrp.SystemBuffer; + + if (PtrIoStackLocation->MajorFunction == IRP_MJ_QUERY_INFORMATION) { + // Now, obtain some parameters. + BufferLength = PtrIoStackLocation->Parameters.QueryFile.Length; + FunctionalityRequested = PtrIoStackLocation->Parameters.QueryFile.FileInformationClass; + + // Acquire the MainResource shared (NOTE: for paging-IO on a + // page file, we should avoid acquiring any resources and simply + // trust the VMM to do the right thing, else we could possibly + // run into deadlocks). + if (!(PtrFCB->FCBFlags & SFSD_FCB_PAGE_FILE)) { + // Acquire the MainResource shared. + if (!ExAcquireResourceSharedLite(&(PtrReqdFCB->MainResource), CanWait)) { + PostRequest = TRUE; + try_return(RC = STATUS_PENDING); + } + MainResourceAcquired = TRUE; + } + + // Do whatever the caller asked us to do + switch (FunctionalityRequested) { + case FileBasicInformation: + RC = SFsdGetBasicInformation(PtrFCB, (PFILE_BASIC_INFORMATION)PtrSystemBuffer, &BufferLength); + break; + case FileStandardInformation: + // RC = SFsdGetStandardInformation(PtrFCB, PtrCCB, ...); + break; + // Similarly, implement all of the other query information routines + // that your FSD can support. +#if(_WIN32_WINNT >= 0x0400) + case FileNetworkOpenInformation: + // RC = SFsdGetNetworkOpenInformation(...); + break; +#endif // _WIN32_WINNT >= 0x0400 + case FileInternalInformation: + // RC = SFsdGetInternalInformation(...); + break; + case FileEaInformation: + // RC = SFsdGetEaInformation(...); + break; + case FileNameInformation: + // RC = SFsdGetFullNameInformation(...); + break; + case FileAlternateNameInformation: + // RC = SFsdGetAltNameInformation(...); + break; + case FileCompressionInformation: + // RC = SFsdGetCompressionInformation(...); + break; + case FilePositionInformation: + // This is fairly simple. Copy over the information from the + // file object. + { + PFILE_POSITION_INFORMATION PtrFileInfoBuffer; + + PtrFileInfoBuffer = (PFILE_POSITION_INFORMATION)PtrSystemBuffer; + + ASSERT(BufferLength >= sizeof(FILE_POSITION_INFORMATION)); + PtrFileInfoBuffer->CurrentByteOffset = PtrFileObject->CurrentByteOffset; + // Modify the local variable for BufferLength appropriately. + BufferLength -= sizeof(FILE_POSITION_INFORMATION); + } + break; + case FileStreamInformation: + // RC = SFsdGetFileStreamInformation(...); + break; + case FileAllInformation: + // The I/O Manager supplies the Mode, Access, and Alignment + // information. The rest is up to us to provide. + // Therefore, decrement the BufferLength appropriately (assuming + // that the above 3 types on information are already in the + // buffer) + { + + PFILE_POSITION_INFORMATION PtrFileInfoBuffer; + PFILE_ALL_INFORMATION PtrAllInfo = (PFILE_ALL_INFORMATION)PtrSystemBuffer; + + BufferLength -= (sizeof(FILE_MODE_INFORMATION) + sizeof(FILE_ACCESS_INFORMATION) + sizeof(FILE_ALIGNMENT_INFORMATION)); + + // Fill in the position information. + + PtrFileInfoBuffer = (PFILE_POSITION_INFORMATION)&(PtrAllInfo->PositionInformation); + + PtrFileInfoBuffer->CurrentByteOffset = PtrFileObject->CurrentByteOffset; + + // Modify the local variable for BufferLength appropriately. + ASSERT(BufferLength >= sizeof(FILE_POSITION_INFORMATION)); + BufferLength -= sizeof(FILE_POSITION_INFORMATION); + + // Get the remaining stuff. + if (!NT_SUCCESS(RC = SFsdGetBasicInformation(PtrFCB, (PFILE_BASIC_INFORMATION)&(PtrAllInfo->BasicInformation), + &BufferLength))) { + // Another method you may wish to use to avoid the + // multiple checks for success/failure is to have the + // called routine simply raise an exception instead. + try_return(RC); + } + // Similarly, get all of the others ... + } + break; + default: + RC = STATUS_INVALID_PARAMETER; + try_return(RC); + } + + // If we completed successfully, the return the amount of information transferred. + if (NT_SUCCESS(RC)) { + PtrIrp->IoStatus.Information = PtrIoStackLocation->Parameters.QueryFile.Length - BufferLength; + } else { + PtrIrp->IoStatus.Information = 0; + } + + } else { + ASSERT(PtrIoStackLocation->MajorFunction == IRP_MJ_SET_INFORMATION); + + // Now, obtain some parameters. + FunctionalityRequested = PtrIoStackLocation->Parameters.SetFile.FileInformationClass; + + // If your FSD supports opportunistic locking (described in + // Chapter 11), then you should check whether the oplock state + // allows the caller to proceed. + + // Rename, and link operations require creation of a directory + // entry and possibly deletion of another directory entry. Since, + // we acquire the VCB resource exclusively during create operations, + // we should acquire it exclusively for link and/or rename operations + // as well. + + // Similarly, marking a directory entry for deletion should cause us + // to acquire the VCB exclusively as well. + + if ((FunctionalityRequested == FileDispositionInformation) || (FunctionalityRequested == FileRenameInformation) || + (FunctionalityRequested == FileLinkInformation)) { + if (!ExAcquireResourceExclusiveLite(&(PtrVCB->VCBResource), CanWait)) { + PostRequest = TRUE; + try_return(RC = STATUS_PENDING); + } + // We have the VCB acquired exclusively. + VCBResourceAcquired = TRUE; + } + + // Unless this is an operation on a page file, we should go ahead and + // acquire the FCB exclusively at this time. Note that we will pretty + // much block out anything being done to the FCB from this point on. + if (!(PtrFCB->FCBFlags & SFSD_FCB_PAGE_FILE)) { + // Acquire the MainResource shared. + if (!ExAcquireResourceExclusiveLite(&(PtrReqdFCB->MainResource), CanWait)) { + PostRequest = TRUE; + try_return(RC = STATUS_PENDING); + } + MainResourceAcquired = TRUE; + } + + // The only operations that could conceivably proceed from this point + // on are paging-IO read/write operations. For delete link (rename), + // set allocation size, and set EOF, should also acquire the paging-IO + // resource, thereby synchronizing with paging-IO requests. In your + // FSD, you should ideally acquire the resource only when processing + // such requests; here however, I will go ahead and block out all + // paging-IO read/write operations at this time (for convenience). + if (!ExAcquireResourceExclusiveLite(&(PtrReqdFCB->PagingIoResource), CanWait)) { + PostRequest = TRUE; + try_return(RC = STATUS_PENDING); + } + + // Do whatever the caller asked us to do + switch (FunctionalityRequested) { + case FileBasicInformation: + RC = SFsdSetBasicInformation(PtrFCB, PtrCCB, PtrFileObject, (PFILE_BASIC_INFORMATION)PtrSystemBuffer); + break; + case FilePositionInformation: + // Check if no intermediate buffering has been specified. + // If it was specified, do not allow non-aligned set file + // position requests to succeed. + { + PFILE_POSITION_INFORMATION PtrFileInfoBuffer; + + PtrFileInfoBuffer = (PFILE_POSITION_INFORMATION)PtrSystemBuffer; + + if (PtrFileObject->Flags & FO_NO_INTERMEDIATE_BUFFERING) { + if (PtrFileInfoBuffer->CurrentByteOffset.LowPart & PtrIoStackLocation->DeviceObject->AlignmentRequirement) { + // Invalid alignment. + try_return(RC = STATUS_INVALID_PARAMETER); + } + } + + PtrFileObject->CurrentByteOffset = PtrFileInfoBuffer->CurrentByteOffset; + } + break; + case FileDispositionInformation: + RC = SFsdSetDispositionInformation(PtrFCB, PtrCCB, PtrVCB, PtrFileObject, PtrIrpContext, PtrIrp, + (PFILE_DISPOSITION_INFORMATION)PtrSystemBuffer); + break; + case FileRenameInformation: + case FileLinkInformation: + // When you implement your rename/link routine, be careful to + // check the following two arguments: + // TargetFileObject = PtrIoStackLocation->Parameters.SetFile.FileObject; + // ReplaceExistingFile = PtrIoStackLocation->Parameters.SetFile.ReplaceIfExists; + // + // The TargetFileObject argument is a pointer to the "target + // directory" file object obtained during the "create" routine + // invoked by the NT I/O Manager with the SL_OPEN_TARGET_DIRECTORY + // flag specified. Remember that it is quite possible that if the + // rename/link is contained within a single directory, the target + // and source directories will be the same. + // The ReplaceExistingFile argument should be used by you to + // determine if the caller wishes to replace the target (if it + // currently exists) with the new link/renamed file. If this + // value is FALSE, and if the target directory entry (being + // renamed-to, or the target of the link) exists, you should + // return a STATUS_OBJECT_NAME_COLLISION error to the caller. + + // RC = SFsdRenameOrLinkFile(PtrFCB, PtrCCB, PtrFileObject, PtrIrpContext, + // PtrIrp, (PFILE_RENAME_INFORMATION)PtrSystemBuffer); + + // Once you have completed the rename/link operation, do not + // forget to notify any "notify IRPs" about the actions you have + // performed. + // An example is if you renamed across directories, you should + // report that a new entry was added with the FILE_ACTION_ADDED + // action type. The actual modification would then be reported + // as either FILE_NOTIFY_CHANGE_FILE_NAME (if a file was renamed) + // or FILE_NOTIFY_CHANGE_DIR_NAME (if a directory was renamed). + break; + case FileAllocationInformation: + RC = SFsdSetAllocationInformation(PtrFCB, PtrCCB, PtrVCB, PtrFileObject, + PtrIrpContext, PtrIrp, PtrSystemBuffer); + break; + case FileEndOfFileInformation: + // RC = SFsdSetEOF(...); + break; + default: + RC = STATUS_INVALID_PARAMETER; + try_return(RC); + } + } + + try_exit: NOTHING; + + } finally { + + if (PagingIoResourceAcquired) { + SFsdReleaseResource(&(PtrReqdFCB->PagingIoResource)); + PagingIoResourceAcquired = FALSE; + } + + if (MainResourceAcquired) { + SFsdReleaseResource(&(PtrReqdFCB->MainResource)); + MainResourceAcquired = FALSE; + } + + if (VCBResourceAcquired) { + SFsdReleaseResource(&(PtrVCB->VCBResource)); + VCBResourceAcquired = FALSE; + } + + // Post IRP if required + if (PostRequest) { + + // Since, the I/O Manager gave us a system buffer, we do not + // need to "lock" anything. + + // Perform the post operation which will mark the IRP pending + // and will return STATUS_PENDING back to us + RC = SFsdPostRequest(PtrIrpContext, PtrIrp); + + } else { + + // Can complete the IRP here if no exception was encountered + if (!(PtrIrpContext->IrpContextFlags & SFSD_IRP_CONTEXT_EXCEPTION)) { + PtrIrp->IoStatus.Status = RC; + + // Free up the Irp Context + SFsdReleaseIrpContext(PtrIrpContext); + + // complete the IRP + IoCompleteRequest(PtrIrp, IO_DISK_INCREMENT); + } + } // can we complete the IRP ? + } // end of "finally" processing + + return(RC); +} + + +/************************************************************************* +* +* Function: SFsdGetBasicInformation() +* +* Description: +* Return some time-stamps and file attributes to the caller. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFsdGetBasicInformation( +PtrSFsdFCB PtrFCB, +PFILE_BASIC_INFORMATION PtrBuffer, +long *PtrReturnedLength) +{ + NTSTATUS RC = STATUS_SUCCESS; + + try { + if (*PtrReturnedLength < sizeof(FILE_BASIC_INFORMATION)) { + try_return(RC = STATUS_BUFFER_OVERFLOW); + } + + // Zero out the supplied buffer. + RtlZeroMemory(PtrBuffer, sizeof(FILE_BASIC_INFORMATION)); + + // Note: If your FSD wishes to be even more precise about time + // stamps, you may wish to consider the effects of fast-IO on the + // file stream. Typically, the FSD simply states a flag indicating + // that fast-IO read/write has occured. Time stamps are then updated + // when a cleanup is received for the file stream. However, if the + // user performs fast-IO and subsequently issues a request to query + // basic information, your FSD could query the current system time + // using KeQuerySystemTime(), and update the FCB time-stamps before + // returning the values to the caller. This gives the caller a slightly + // more accurate value. + + // Get information from the FCB. + PtrBuffer->CreationTime = PtrFCB->CreationTime; + PtrBuffer->LastAccessTime = PtrFCB->LastAccessTime; + PtrBuffer->LastWriteTime = PtrFCB->LastWriteTime; + // Assume that the sample FSD does not support a "change time". + + // Now fill in the attributes. + PtrBuffer->FileAttributes = FILE_ATTRIBUTE_NORMAL; + + if (PtrFCB->FCBFlags & SFSD_FCB_DIRECTORY) { + PtrBuffer->FileAttributes |= FILE_ATTRIBUTE_DIRECTORY; + } + + // Similarly, fill in attributes indicating a hidden file, system + // file, compressed file, temporary file, etc. if your FSD supports + // such file attribute values. + + try_exit: NOTHING; + } finally { + if (NT_SUCCESS(RC)) { + // Return the amount of information filled in. + *PtrReturnedLength -= sizeof(FILE_BASIC_INFORMATION); + } + } + return(RC); +} + + +/************************************************************************* +* +* Function: SFsdSetBasicInformation() +* +* Description: +* Set some time-stamps and file attributes supplied by the caller. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFsdSetBasicInformation( +PtrSFsdFCB PtrFCB, +PtrSFsdCCB PtrCCB, +PFILE_OBJECT PtrFileObject, +PFILE_BASIC_INFORMATION PtrBuffer) +{ + NTSTATUS RC = STATUS_SUCCESS; + BOOLEAN CreationTimeChanged = FALSE; + BOOLEAN AttributesChanged = FALSE; + + try { + + // Obtain a pointer to the directory entry associated with + // the FCB being modifed. The directory entry is obviously + // part of the data associated with the parent directory that + // contains this particular file stream. + // Note well that no other modifications + // are currently allowed to the directory entry since we have + // the VCB resource exclusively acquired (as a matter of fact, + // NO directory on the logical volume can be currently modified). + // PtrDirectoryEntry = SFsdGetDirectoryEntryPtr(...); + + if (RtlLargeIntegerNotEqualToZero(PtrBuffer->CreationTime)) { + // Modify the directory entry timestamp. + // ... + + // Also note that fact that the timestamp has changed + // so that any directory notifications can be performed. + CreationTimeChanged = TRUE; + + // The interesting thing here is that the user has set certain time + // fields. However, before doing this, the user may have performed + // I/O which in turn would have caused your FSD to mark the fact that + // write/access time should be modifed at cleanup. + // You might wish to mark the fact that such updates are no longer + // required since the user has explicitly specified the values she + // wishes to see associated with the file stream. + SFsdSetFlag(PtrCCB->CCBFlags, SFSD_CCB_CREATE_TIME_SET); + } + + // Similarly, check for all the time-stamp values that your + // FSD cares about. Ignore the ones that you do not support. + // ... + + + // Now come the attributes. + if (PtrBuffer->FileAttributes) { + // We have a non-zero attribute value. + // The presence of a particular attribute indicates that the + // user wishes to set the attribute value. The absence indicates + // the user wishes to clear the particular attribute. + + // Before we start examining attribute values, you may wish + // to clear any unsupported attribute flags to reduce confusion. + + SFsdClearFlag(PtrBuffer->FileAttributes, ~FILE_ATTRIBUTE_VALID_SET_FLAGS); + SFsdClearFlag(PtrBuffer->FileAttributes, FILE_ATTRIBUTE_NORMAL); + + // Similarly, you should pick out other invalid flag values. + // SFsdClearFlag(PtrBuffer->FileAttributes, FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_ATOMIC_WRITE ...); + + + if (PtrBuffer->FileAttributes & FILE_ATTRIBUTE_TEMPORARY) { + SFsdSetFlag(PtrFileObject->Flags, FO_TEMPORARY_FILE); + } else { + SFsdClearFlag(PtrFileObject->Flags, FO_TEMPORARY_FILE); + } + + // If your FSD supports file compression, you may wish to + // note the user's preferences for compressing/not compressing + // the file at this time. + } + + try_exit: NOTHING; + } finally { + ; + } + return(RC); +} + + +/************************************************************************* +* +* Function: SFsdSetDispositionInformation() +* +* Description: +* Mark/Unmark a file for deletion. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFsdSetDispositionInformation( +PtrSFsdFCB PtrFCB, +PtrSFsdCCB PtrCCB, +PtrSFsdVCB PtrVCB, +PFILE_OBJECT PtrFileObject, +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp, +PFILE_DISPOSITION_INFORMATION PtrBuffer) +{ + NTSTATUS RC = STATUS_SUCCESS; + + try { + if (!PtrBuffer->DeleteFile) { + // "un-delete" the file. + SFsdClearFlag(PtrFCB->FCBFlags, SFSD_FCB_DELETE_ON_CLOSE); + PtrFileObject->DeletePending = FALSE; + try_return(RC); + } + + // The easy part is over. Now, we know that the user wishes to + // delete the corresponding directory entry (of course, if this + // is the only link to the file stream, any on-disk storage space + // associated with the file stream will also be released when the + // (only) link is deleted!) + + // Do some checking to see if the file can even be deleted. + + if (PtrFCB->FCBFlags & SFSD_FCB_DELETE_ON_CLOSE) { + // All done! + try_return(RC); + } + + if (PtrFCB->FCBFlags & SFSD_FCB_READ_ONLY) { + try_return(RC = STATUS_CANNOT_DELETE); + } + + if (PtrVCB->VCBFlags & SFSD_VCB_FLAGS_VOLUME_READ_ONLY) { + try_return(RC = STATUS_CANNOT_DELETE); + } + + // An important step is to check if the file stream has been + // mapped by any process. The delete cannot be allowed to proceed + // in this case. + if (!MmFlushImageSection(&(PtrFCB->NTRequiredFCB.SectionObject), MmFlushForDelete)) { + try_return(RC = STATUS_CANNOT_DELETE); + } + + // It would not be prudent to allow deletion of either a root + // directory or a directory that is not empty. + if (PtrFCB->FCBFlags & SFSD_FCB_ROOT_DIRECTORY) { + try_return(RC = STATUS_CANNOT_DELETE); + } + + if (PtrFCB->FCBFlags & SFSD_FCB_DIRECTORY) { + // Perform your check to determine whether the directory + // is empty or not. + // if (!SFsdIsDirectoryEmpty(PtrFCB, PtrCCB, PtrIrpContext)) { + // try_return(RC = STATUS_DIRECTORY_NOT_EMPTY); + // } + } + + // Set a flag to indicate that this directory entry will become history + // at cleanup. + SFsdSetFlag(PtrFCB->FCBFlags, SFSD_FCB_DELETE_ON_CLOSE); + PtrFileObject->DeletePending = TRUE; + + try_exit: NOTHING; + } finally { + ; + } + return(RC); +} + + + +/************************************************************************* +* +* Function: SFsdSetAllocationInformation() +* +* Description: +* Mark/Unmark a file for deletion. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFsdSetAllocationInformation( +PtrSFsdFCB PtrFCB, +PtrSFsdCCB PtrCCB, +PtrSFsdVCB PtrVCB, +PFILE_OBJECT PtrFileObject, +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp, +PFILE_ALLOCATION_INFORMATION PtrBuffer) +{ + NTSTATUS RC = STATUS_SUCCESS; + BOOLEAN TruncatedFile = FALSE; + BOOLEAN ModifiedAllocSize = FALSE; + + try { + // Increasing the allocation size associated with a file stream + // is relatively easy. All you have to do is execute some FSD + // specific code to check whether you have enough space available + // (and if your FSD supports user/volume quotas, whether the user + // is not exceeding quota), and then increase the file size in the + // corresponding on-disk and in-memory structures. + // Then, all you should do is inform the Cache Manager about the + // increased allocation size. + + // First, do whatever error checking is appropriate here (e.g. whether + // the caller is trying the change size for a directory, etc.). + + // Are we increasing the allocation size? + if (RtlLargeIntegerLessThan( + PtrFCB->NTRequiredFCB.CommonFCBHeader.AllocationSize, + PtrBuffer->AllocationSize)) { + + // Yes. Do the FSD specific stuff i.e. increase reserved + // space on disk. + // RC = SFsdTruncateFileAllocationSize(...) + + ModifiedAllocSize = TRUE; + + } else if (RtlLargeIntegerGreaterThan(PtrFCB->NTRequiredFCB.CommonFCBHeader.AllocationSize, + PtrBuffer->AllocationSize)) { + // This is the painful part. See if the VMM will allow us to proceed. + // The VMM will deny the request if: + // (a) any image section exists OR + // (b) a data section exists and the size of the user mapped view + // is greater than the new size + // Otherwise, the VMM should allow the request to proceed. + if (!MmCanFileBeTruncated(&(PtrFCB->NTRequiredFCB.SectionObject), &(PtrBuffer->AllocationSize))) { + // VMM said no way! + try_return(RC = STATUS_USER_MAPPED_FILE); + } + + // Perform your directory entry modifications. Release any on-disk + // space you may need to in the process. + // RC = SFsdTruncateFileAllocationSize(...); + + ModifiedAllocSize = TRUE; + TruncatedFile = TRUE; + } + + try_exit: + + // This is a good place to check if we have performed a truncate + // operation. If we have perform a truncate (whether we extended + // or reduced file size), you should update file time stamps. + + // Last, but not the lease, you must inform the Cache Manager of file size changes. + if (ModifiedAllocSize && NT_SUCCESS(RC)) { + // Update the FCB Header with the new allocation size. + PtrFCB->NTRequiredFCB.CommonFCBHeader.AllocationSize = PtrBuffer->AllocationSize; + + // If we decreased the allocation size to less than the + // current file size, modify the file size value. + // Similarly, if we decreased the value to less than the + // current valid data length, modify that value as well. + if (TruncatedFile) { + if (RtlLargeIntegerLessThan(PtrFCB->NTRequiredFCB.CommonFCBHeader.FileSize, PtrBuffer->AllocationSize)) { + // Decrease the file size value. + PtrFCB->NTRequiredFCB.CommonFCBHeader.FileSize = PtrBuffer->AllocationSize; + } + + if (RtlLargeIntegerLessThan(PtrFCB->NTRequiredFCB.CommonFCBHeader.ValidDataLength, PtrBuffer->AllocationSize)) { + // Decrease the valid data length value. + PtrFCB->NTRequiredFCB.CommonFCBHeader.ValidDataLength = PtrBuffer->AllocationSize; + } + } + + + // If the FCB has not had caching initiated, it is still valid + // for you to invoke the NT Cache Manager. It is possible in such + // situations for the call to be no'oped (unless some user has + // mapped in the file) + + // NOTE: The invocation to CcSetFileSizes() will quite possibly + // result in a recursive call back into the file system. + // This is because the NT Cache Manager will typically + // perform a flush before telling the VMM to purge pages + // especially when caching has not been initiated on the + // file stream, but the user has mapped the file into + // the process' virtual address space. + CcSetFileSizes(PtrFileObject, (PCC_FILE_SIZES)&(PtrFCB->NTRequiredFCB.CommonFCBHeader.AllocationSize)); + + // Inform any pending IRPs (notify change directory). + } + + } finally { + ; + } + return(RC); +} + + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/src/flush.c`: + +```c +/************************************************************************* +* +* File: flush.c +* +* Module: Sample File System Driver (Kernel mode execution only) +* +* Description: +* Contains code to handle the "Flush Buffers" dispatch entry point. +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +#include "sfsd.h" + +// define the file specific bug-check id +#define SFSD_BUG_CHECK_ID SFSD_FILE_FLUSH + + + +/************************************************************************* +* +* Function: SFsdFlush() +* +* Description: +* The I/O Manager will invoke this routine to handle a flush buffers +* request +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL (invocation at higher IRQL will cause execution +* to be deferred to a worker thread context) +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFsdFlush( +PDEVICE_OBJECT DeviceObject, // the logical volume device object +PIRP Irp) // I/O Request Packet +{ + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdIrpContext PtrIrpContext = NULL; + BOOLEAN AreWeTopLevel = FALSE; + + FsRtlEnterFileSystem(); + ASSERT(DeviceObject); + ASSERT(Irp); + + // set the top level context + AreWeTopLevel = SFsdIsIrpTopLevel(Irp); + + try { + + // get an IRP context structure and issue the request + PtrIrpContext = SFsdAllocateIrpContext(Irp, DeviceObject); + ASSERT(PtrIrpContext); + + RC = SFsdCommonFlush(PtrIrpContext, Irp); + + } except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) { + + RC = SFsdExceptionHandler(PtrIrpContext, Irp); + + SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC); + } + + if (AreWeTopLevel) { + IoSetTopLevelIrp(NULL); + } + + FsRtlExitFileSystem(); + + return(RC); +} + + + +/************************************************************************* +* +* Function: SFsdCommonFlush() +* +* Description: +* The actual work is performed here. This routine may be invoked in one' +* of the two possible contexts: +* (a) in the context of a system worker thread +* (b) in the context of the original caller +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFsdCommonFlush( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp) +{ + NTSTATUS RC = STATUS_SUCCESS; + PIO_STACK_LOCATION PtrIoStackLocation = NULL; + PFILE_OBJECT PtrFileObject = NULL; + PtrSFsdFCB PtrFCB = NULL; + PtrSFsdCCB PtrCCB = NULL; + PtrSFsdVCB PtrVCB = NULL; + PtrSFsdNTRequiredFCB PtrReqdFCB = NULL; + BOOLEAN AcquiredFCB = FALSE; + BOOLEAN PostRequest = FALSE; + BOOLEAN CanWait = TRUE; + + try { + // First, get a pointer to the current I/O stack location + PtrIoStackLocation = IoGetCurrentIrpStackLocation(PtrIrp); + ASSERT(PtrIoStackLocation); + + PtrFileObject = PtrIoStackLocation->FileObject; + ASSERT(PtrFileObject); + + // Get the FCB and CCB pointers + PtrCCB = (PtrSFsdCCB)(PtrFileObject->FsContext2); + ASSERT(PtrCCB); + PtrFCB = PtrCCB->PtrFCB; + ASSERT(PtrFCB); + PtrReqdFCB = &(PtrFCB->NTRequiredFCB); + + // Get some of the parameters supplied to us + CanWait = ((PtrIrpContext->IrpContextFlags & SFSD_IRP_CONTEXT_CAN_BLOCK) ? TRUE : FALSE); + + // If we cannot wait, post the request immediately since a flush is inherently blocking/synchronous. + if (!CanWait) { + PostRequest = TRUE; + try_return(RC); + } + + // Check the type of object passed-in. That will determine the course of + // action we take. + if ((PtrFCB->NodeIdentifier.NodeType == SFSD_NODE_TYPE_VCB) || (PtrFCB->FCBFlags & SFSD_FCB_ROOT_DIRECTORY)) { + + if (PtrFCB->NodeIdentifier.NodeType == SFSD_NODE_TYPE_VCB) { + PtrVCB = (PtrSFsdVCB)(PtrFCB); + } else { + PtrVCB = PtrFCB->PtrVCB; + } + + // The caller wishes to flush all files for the mounted + // logical volume. The flush volume routine below should simply + // walk through all of the open file streams, acquire the + // FCB resource, and request the flush operation from the Cache + // Manager. Basically, the sequence of operations listed below + // for a single file should be executed on all open files. + + SFsdFlushLogicalVolume(PtrIrpContext, PtrIrp, PtrVCB); + + try_return(RC); + } + + if (!(PtrFCB->FCBFlags & SFSD_FCB_DIRECTORY)) { + // This is a regular file. + ExAcquireResourceExclusiveLite(&(PtrReqdFCB->MainResource), TRUE); + AcquiredFCB = TRUE; + + // Request the Cache Manager to perform a flush operation. + // Further, instruct the Cache Manager that we wish to flush the + // entire file stream. + SFsdFlushAFile(PtrReqdFCB, &(PtrIrp->IoStatus)); + RC = PtrIrp->IoStatus.Status; + // All done. You may want to also flush the directory entry for the + // file stream at this time. + + // Some log-based FSD implementations may wish to flush their + // log files at this time. Finally, you should update the time-stamp + // values for the file stream appropriately. This would involve + // obtaining the current time and modifying the appropriate directory + // entry fields. + } + + try_exit: + + if (AcquiredFCB) { + SFsdReleaseResource(&(PtrReqdFCB->MainResource)); + AcquiredFCB = FALSE; + } + + if (!PostRequest) { + PIO_STACK_LOCATION PtrNextIoStackLocation = NULL; + NTSTATUS RC1 = STATUS_SUCCESS; + + // Send the request down at this point. + // To do this, you must set the next IRP stack location, and + // maybe set a completion routine. + // Be careful about marking the IRP pending if the lower level + // driver returned pending and you do have a completion routine! + PtrNextIoStackLocation = IoGetNextIrpStackLocation(PtrIrp); + *PtrNextIoStackLocation = *PtrIoStackLocation; + + // Set the completion routine to "eat-up" any + // STATUS_INVALID_DEVICE_REQUEST error code returned by the lower + // level driver. + IoSetCompletionRoutine(PtrIrp, SFsdFlushCompletion, NULL, TRUE, TRUE, TRUE); + + RC1 = IoCallDriver(PtrVCB->TargetDeviceObject, PtrIrp); + + RC = ((RC1 == STATUS_INVALID_DEVICE_REQUEST) ? RC : RC1); + } + + } finally { + if (PostRequest) { + // Nothing to lock now. + RC = SFsdPostRequest(PtrIrpContext, PtrIrp); + } else { + // Release the IRP context at this time. + SFsdReleaseIrpContext(PtrIrpContext); + } + } + + return(RC); +} + + +/************************************************************************* +* +* Function: SFsdFlushAFile() +* +* Description: +* Tell the Cache Manager to perform a flush. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: None +* +*************************************************************************/ +void SFsdFlushAFile( +PtrSFsdNTRequiredFCB PtrReqdFCB, +PIO_STATUS_BLOCK PtrIoStatus) +{ + CcFlushCache(&(PtrReqdFCB->SectionObject), NULL, 0, PtrIoStatus); + return; +} + + +/************************************************************************* +* +* Function: SFsdFlushLogicalVolume() +* +* Description: +* Flush everything beginning at root directory. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: None +* +*************************************************************************/ +void SFsdFlushLogicalVolume( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp, +PtrSFsdVCB PtrVCB) +{ + BOOLEAN AcquiredVCB = FALSE; + PtrSFsdFCB PtrFCB = NULL; + PLIST_ENTRY PtrNextFCB = NULL; + + try { + ExAcquireResourceExclusiveLite(&(PtrVCB->VCBResource), TRUE); + AcquiredVCB = TRUE; + + // Go through the list of FCB's. You would probably + // flush all of the files. Then, you could flush the + // directories that you may have have pinned into memory. + + // NOTE: This function may also be invoked internally as part of + // processing a shutdown request. + + } finally { + + if (AcquiredVCB) { + SFsdReleaseResource(&(PtrVCB->VCBResource)); + } + } + + return; +} + + +/************************************************************************* +* +* Function: SFsdFlushCompletion() +* +* Description: +* Eat up any bad errors. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: None +* +*************************************************************************/ +NTSTATUS SFsdFlushCompletion( +PDEVICE_OBJECT PtrDeviceObject, +PIRP PtrIrp, +PVOID Context) +{ + NTSTATUS RC = STATUS_SUCCESS; + + if (PtrIrp->PendingReturned) { + IoMarkIrpPending(PtrIrp); + } + + if (PtrIrp->IoStatus.Status == STATUS_INVALID_DEVICE_REQUEST) { + // cannot do much here, can we? + PtrIrp->IoStatus.Status = STATUS_SUCCESS; + } + + return(STATUS_SUCCESS); +} + + + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/src/makefile`: + +``` +# +# DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source +# file to this component. This file merely indirects to the real make file +# that is shared by all the driver components of the Windows NT DDK +# + +!INCLUDE $(NTMAKEENV)\makefile.def + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/src/misc.c`: + +```c +/************************************************************************* +* +* File: misc.c +* +* Module: Sample File System Driver (Kernel mode execution only) +* +* Description: +* This file contains some miscellaneous support routines. +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +#include "sfsd.h" + +// define the file specific bug-check id +#define SFSD_BUG_CHECK_ID SFSD_FILE_MISC + + +/************************************************************************* +* +* Function: SFsdInitializeZones() +* +* Description: +* Allocates some memory for global zones used to allocate FSD structures. +* Either all memory will be allocated or we will back out gracefully. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFsdInitializeZones( +void) +{ + NTSTATUS RC = STATUS_SUCCESS; + uint32 SizeOfZone = SFsdGlobalData.DefaultZoneSizeInNumStructs; + uint32 SizeOfObjectNameZone = 0; + uint32 SizeOfCCBZone = 0; + uint32 SizeOfFCBZone = 0; + uint32 SizeOfByteLockZone = 0; + uint32 SizeOfIrpContextZone = 0; + + try { + + // initialize the spinlock protecting the zones + KeInitializeSpinLock(&(SFsdGlobalData.ZoneAllocationSpinLock)); + + // determine memory requirements + switch (MmQuerySystemSize()) { + case MmSmallSystem: + // this is just for illustration purposes. I will multiply + // number of structures with some arbitrary amount depending + // upon available memory in the system ... You should choose a + // more intelligent method suitable to your memory consumption + // and the amount of memory available. + SizeOfObjectNameZone = (2 * SizeOfZone * SFsdQuadAlign(sizeof(SFsdObjectName))) + sizeof(ZONE_SEGMENT_HEADER); + SizeOfCCBZone = (2 * SizeOfZone * SFsdQuadAlign(sizeof(SFsdCCB))) + sizeof(ZONE_SEGMENT_HEADER); + SizeOfFCBZone = (2 * SizeOfZone * SFsdQuadAlign(sizeof(SFsdFCB))) + sizeof(ZONE_SEGMENT_HEADER); + SizeOfByteLockZone = (2 * SizeOfZone * SFsdQuadAlign(sizeof(SFsdFileLockInfo))) + sizeof(ZONE_SEGMENT_HEADER); + SizeOfIrpContextZone = (2 * SizeOfZone * SFsdQuadAlign(sizeof(SFsdIrpContext))) + sizeof(ZONE_SEGMENT_HEADER); + break; + case MmMediumSystem: + SizeOfObjectNameZone = (4 * SizeOfZone * SFsdQuadAlign(sizeof(SFsdObjectName))) + sizeof(ZONE_SEGMENT_HEADER); + SizeOfCCBZone = (4 * SizeOfZone * SFsdQuadAlign(sizeof(SFsdCCB))) + sizeof(ZONE_SEGMENT_HEADER); + SizeOfFCBZone = (4 * SizeOfZone * SFsdQuadAlign(sizeof(SFsdFCB))) + sizeof(ZONE_SEGMENT_HEADER); + SizeOfByteLockZone = (4 * SizeOfZone * SFsdQuadAlign(sizeof(SFsdFileLockInfo))) + sizeof(ZONE_SEGMENT_HEADER); + SizeOfIrpContextZone = (4 * SizeOfZone * SFsdQuadAlign(sizeof(SFsdIrpContext))) + sizeof(ZONE_SEGMENT_HEADER); + break; + case MmLargeSystem: + SizeOfObjectNameZone = (8 * SizeOfZone * SFsdQuadAlign(sizeof(SFsdObjectName))) + sizeof(ZONE_SEGMENT_HEADER); + SizeOfCCBZone = (8 * SizeOfZone * SFsdQuadAlign(sizeof(SFsdCCB))) + sizeof(ZONE_SEGMENT_HEADER); + SizeOfFCBZone = (8 * SizeOfZone * SFsdQuadAlign(sizeof(SFsdFCB))) + sizeof(ZONE_SEGMENT_HEADER); + SizeOfByteLockZone = (8 * SizeOfZone * SFsdQuadAlign(sizeof(SFsdFileLockInfo))) + sizeof(ZONE_SEGMENT_HEADER); + SizeOfIrpContextZone = (8 * SizeOfZone * SFsdQuadAlign(sizeof(SFsdIrpContext))) + sizeof(ZONE_SEGMENT_HEADER); + break; + } + + // typical NT methodology (at least until *someone* exposed the "difference" between a server and workstation ;-) + if (MmIsThisAnNtAsSystem()) { + SizeOfObjectNameZone *= SFSD_NTAS_MULTIPLE; + SizeOfCCBZone *= SFSD_NTAS_MULTIPLE; + SizeOfFCBZone *= SFSD_NTAS_MULTIPLE; + SizeOfByteLockZone *= SFSD_NTAS_MULTIPLE; + SizeOfIrpContextZone *= SFSD_NTAS_MULTIPLE; + } + + // allocate memory for each of the zones and initialize the zones ... + if (!(SFsdGlobalData.ObjectNameZone = ExAllocatePool(NonPagedPool, SizeOfObjectNameZone))) { + RC = STATUS_INSUFFICIENT_RESOURCES; + try_return(RC); + } + + if (!(SFsdGlobalData.CCBZone = ExAllocatePool(NonPagedPool, SizeOfCCBZone))) { + RC = STATUS_INSUFFICIENT_RESOURCES; + try_return(RC); + } + + if (!(SFsdGlobalData.FCBZone = ExAllocatePool(NonPagedPool, SizeOfFCBZone))) { + RC = STATUS_INSUFFICIENT_RESOURCES; + try_return(RC); + } + + if (!(SFsdGlobalData.ByteLockZone = ExAllocatePool(NonPagedPool, SizeOfByteLockZone))) { + RC = STATUS_INSUFFICIENT_RESOURCES; + try_return(RC); + } + + if (!(SFsdGlobalData.IrpContextZone = ExAllocatePool(NonPagedPool, SizeOfIrpContextZone))) { + RC = STATUS_INSUFFICIENT_RESOURCES; + try_return(RC); + } + + // initialize each of the zone headers ... + if (!NT_SUCCESS(RC = ExInitializeZone(&(SFsdGlobalData.ObjectNameZoneHeader), + SFsdQuadAlign(sizeof(SFsdObjectName)), + SFsdGlobalData.ObjectNameZone, SizeOfObjectNameZone))) { + // failed the initialization, leave ... + try_return(RC); + } + + if (!NT_SUCCESS(RC = ExInitializeZone(&(SFsdGlobalData.CCBZoneHeader), + SFsdQuadAlign(sizeof(SFsdCCB)), + SFsdGlobalData.CCBZone, + SizeOfCCBZone))) { + // failed the initialization, leave ... + try_return(RC); + } + + if (!NT_SUCCESS(RC = ExInitializeZone(&(SFsdGlobalData.FCBZoneHeader), + SFsdQuadAlign(sizeof(SFsdFCB)), + SFsdGlobalData.FCBZone, + SizeOfFCBZone))) { + // failed the initialization, leave ... + try_return(RC); + } + + if (!NT_SUCCESS(RC = ExInitializeZone(&(SFsdGlobalData.ByteLockZoneHeader), + SFsdQuadAlign(sizeof(SFsdFileLockInfo)), + SFsdGlobalData.ByteLockZone, + SizeOfByteLockZone))) { + // failed the initialization, leave ... + try_return(RC); + } + + if (!NT_SUCCESS(RC = ExInitializeZone(&(SFsdGlobalData.IrpContextZoneHeader), + SFsdQuadAlign(sizeof(SFsdIrpContext)), + SFsdGlobalData.IrpContextZone, + SizeOfIrpContextZone))) { + // failed the initialization, leave ... + try_return(RC); + } + + try_exit: NOTHING; + + } finally { + if (!NT_SUCCESS(RC)) { + // invoke the destroy routine now ... + SFsdDestroyZones(); + } else { + // mark the fact that we have allocated zones ... + SFsdSetFlag(SFsdGlobalData.SFsdFlags, SFSD_DATA_FLAGS_ZONES_INITIALIZED); + } + } + + return(RC); +} + + +/************************************************************************* +* +* Function: SFsdDestroyZones() +* +* Description: +* Free up the previously allocated memory. NEVER do this once the +* driver has been successfully loaded. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: None +* +*************************************************************************/ +void SFsdDestroyZones( +void) +{ + try { + // free up each of the pools + ExFreePool(SFsdGlobalData.ObjectNameZone); + ExFreePool(SFsdGlobalData.CCBZone); + ExFreePool(SFsdGlobalData.FCBZone); + ExFreePool(SFsdGlobalData.ByteLockZone); + ExFreePool(SFsdGlobalData.IrpContextZone); + + try_exit: NOTHING; + + } finally { + SFsdClearFlag(SFsdGlobalData.SFsdFlags, SFSD_DATA_FLAGS_ZONES_INITIALIZED); + } + + return; +} + + +/************************************************************************* +* +* Function: SFsdIsIrpTopLevel() +* +* Description: +* Helps the FSD determine who the "top level" caller is for this +* request. A request can originate directly from a user process +* (in which case, the "top level" will be NULL when this routine +* is invoked), OR the user may have originated either from the NT +* Cache Manager/VMM ("top level" may be set), or this could be a +* recursion into our code in which we would have set the "top level" +* field the last time around. +* +* Expected Interrupt Level (for execution) : +* +* whatever level a particular dispatch routine is invoked at. +* +* Return Value: TRUE/FALSE (TRUE if top level was NULL when routine invoked) +* +*************************************************************************/ +BOOLEAN SFsdIsIrpTopLevel( +PIRP Irp) // the IRP sent to our dispatch routine +{ + BOOLEAN ReturnCode = FALSE; + + if (IoGetTopLevelIrp() == NULL) { + // OK, so we can set ourselves to become the "top level" component + IoSetTopLevelIrp(Irp); + ReturnCode = TRUE; + } + + return(ReturnCode); +} + + +/************************************************************************* +* +* Function: SFsdExceptionFilter() +* +* Description: +* This routines allows the driver to determine whether the exception +* is an "allowed" exception i.e. one we should not-so-quietly consume +* ourselves, or one which should be propagated onwards in which case +* we will most likely bring down the machine. +* +* This routine employs the services of FsRtlIsNtstatusExpected(). This +* routine returns a BOOLEAN result. A RC of FALSE will cause us to return +* EXCEPTION_CONTINUE_SEARCH which will probably cause a panic. +* The FsRtl.. routine returns FALSE iff exception values are (currently) : +* STATUS_DATATYPE_MISALIGNMENT || STATUS_ACCESS_VIOLATION || +* STATUS_ILLEGAL_INSTRUCTION || STATUS_INSTRUCTION_MISALIGNMENT +* +* Expected Interrupt Level (for execution) : +* +* ? +* +* Return Value: EXCEPTION_EXECUTE_HANDLER/EXECEPTION_CONTINUE_SEARCH +* +*************************************************************************/ +long SFsdExceptionFilter( +PtrSFsdIrpContext PtrIrpContext, +PEXCEPTION_POINTERS PtrExceptionPointers) +{ + long ReturnCode = EXCEPTION_EXECUTE_HANDLER; + NTSTATUS ExceptionCode = STATUS_SUCCESS; + + // figure out the exception code + ExceptionCode = PtrExceptionPointers->ExceptionRecord->ExceptionCode; + + if ((ExceptionCode == STATUS_IN_PAGE_ERROR) && (PtrExceptionPointers->ExceptionRecord->NumberParameters >= 3)) { + ExceptionCode = PtrExceptionPointers->ExceptionRecord->ExceptionInformation[2]; + } + + if (PtrIrpContext) { + PtrIrpContext->SavedExceptionCode = ExceptionCode; + SFsdSetFlag(PtrIrpContext->IrpContextFlags, SFSD_IRP_CONTEXT_EXCEPTION); + } + + // check if we should propagate this exception or not + if (!(FsRtlIsNtstatusExpected(ExceptionCode))) { + // we are not ok, propagate this exception. + // NOTE: we will bring down the machine ... + ReturnCode = EXCEPTION_CONTINUE_SEARCH; + + // better free up the IrpContext now ... + if (PtrIrpContext) { + SFsdReleaseIrpContext(PtrIrpContext); + } + } + + // if you wish to perform some special processing when + // not propagating the exception, set up the state for + // special processing now ... + + // return the appropriate code + return(ReturnCode); +} + + +/************************************************************************* +* +* Function: SFsdExceptionHandler() +* +* Description: +* One of the routines in the FSD or in the modules we invoked encountered +* an exception. We have decided that we will "handle" the exception. +* Therefore we will prevent the machine from a panic ... +* You can do pretty much anything you choose to in your commercial +* driver at this point to ensure a graceful exit. In the sample +* driver, I will simply free up the IrpContext (if any), set the +* error code in the IRP and complete the IRP at this time ... +* +* Expected Interrupt Level (for execution) : +* +* ? +* +* Return Value: Error code +* +*************************************************************************/ +NTSTATUS SFsdExceptionHandler( +PtrSFsdIrpContext PtrIrpContext, +PIRP Irp) +{ + NTSTATUS RC; + + ASSERT(Irp); + + if (PtrIrpContext) { + RC = PtrIrpContext->SavedExceptionCode; + // Free irp context here + SFsdReleaseIrpContext(PtrIrpContext); + } else { + // must be insufficient resources ...? + RC = STATUS_INSUFFICIENT_RESOURCES; + } + + // set the error code in the IRP + Irp->IoStatus.Status = RC; + Irp->IoStatus.Information = 0; + + // complete the IRP + IoCompleteRequest(Irp, IO_NO_INCREMENT); + + return(RC); +} + + + +/************************************************************************* +* +* Function: SFsdLogEvent() +* +* Description: +* Log a message in the NT Event Log. This is a rather simplistic log +* methodology since you can potentially utilize the event log to +* provide a lot of information to the user (and you should too!) +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: None +* +*************************************************************************/ +void SFsdLogEvent( +NTSTATUS SFsdEventLogId, // the SFsd private message id +NTSTATUS RC) // any NT error code we wish to log ... +{ + try { + + // Implement a call to IoAllocateErrorLogEntry() followed by a call + // to IoWriteErrorLogEntry(). You should note that the call to IoWriteErrorLogEntry() + // will free memory for the entry once the write completes (which in actuality + // is an asynchronous operation). + + } except (EXCEPTION_EXECUTE_HANDLER) { + // nothing really we can do here, just do not wish to crash ... + NOTHING; + } + + return; +} + + +/************************************************************************* +* +* Function: SFsdAllocateObjectName() +* +* Description: +* Allocate a new ObjectName structure to represent an open on-disk object. +* Also initialize the ObjectName structure to NULL. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: A pointer to the ObjectName structure OR NULL. +* +*************************************************************************/ +PtrSFsdObjectName SFsdAllocateObjectName( +void) +{ + PtrSFsdObjectName PtrObjectName = NULL; + BOOLEAN AllocatedFromZone = TRUE; + KIRQL CurrentIrql; + + // first, try to allocate out of the zone + KeAcquireSpinLock(&(SFsdGlobalData.ZoneAllocationSpinLock), &CurrentIrql); + if (!ExIsFullZone(&(SFsdGlobalData.ObjectNameZoneHeader))) { + // we have enough memory + PtrObjectName = (PtrSFsdObjectName)ExAllocateFromZone(&(SFsdGlobalData.ObjectNameZoneHeader)); + + // release the spinlock + KeReleaseSpinLock(&(SFsdGlobalData.ZoneAllocationSpinLock), CurrentIrql); + } else { + // release the spinlock + KeReleaseSpinLock(&(SFsdGlobalData.ZoneAllocationSpinLock), CurrentIrql); + + // if we failed to obtain from the zone, get it directly from the VMM + PtrObjectName = (PtrSFsdObjectName)ExAllocatePool(NonPagedPool, SFsdQuadAlign(sizeof(SFsdObjectName))); + AllocatedFromZone = FALSE; + } + + // if we could not obtain the required memory, bug-check. + // Do NOT do this in your commercial driver, instead handle the error gracefully ... + if (!PtrObjectName) { + SFsdPanic(STATUS_INSUFFICIENT_RESOURCES, SFsdQuadAlign(sizeof(SFsdObjectName)), 0); + } + + // zero out the allocated memory block + RtlZeroMemory(PtrObjectName, SFsdQuadAlign(sizeof(SFsdObjectName))); + + // set up some fields ... + PtrObjectName->NodeIdentifier.NodeType = SFSD_NODE_TYPE_OBJECT_NAME; + PtrObjectName->NodeIdentifier.NodeSize = SFsdQuadAlign(sizeof(SFsdObjectName)); + + + if (!AllocatedFromZone) { + SFsdSetFlag(PtrObjectName->ObjectNameFlags, SFSD_OB_NAME_NOT_FROM_ZONE); + } + + return(PtrObjectName); +} + + +/************************************************************************* +* +* Function: SFsdReleaseObjectName() +* +* Description: +* Deallocate a previously allocated structure. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: None +* +*************************************************************************/ +void SFsdReleaseObjectName( +PtrSFsdObjectName PtrObjectName) +{ + KIRQL CurrentIrql; + + ASSERT(PtrObjectName); + + // give back memory either to the zone or to the VMM + if (!(PtrObjectName->ObjectNameFlags & SFSD_OB_NAME_NOT_FROM_ZONE)) { + // back to the zone + KeAcquireSpinLock(&(SFsdGlobalData.ZoneAllocationSpinLock), &CurrentIrql); + ExFreeToZone(&(SFsdGlobalData.ObjectNameZoneHeader), PtrObjectName); + KeReleaseSpinLock(&(SFsdGlobalData.ZoneAllocationSpinLock), CurrentIrql); + } else { + ExFreePool(PtrObjectName); + } + + return; +} + + +/************************************************************************* +* +* Function: SFsdAllocateCCB() +* +* Description: +* Allocate a new CCB structure to represent an open on-disk object. +* Also initialize the CCB structure to NULL. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: A pointer to the CCB structure OR NULL. +* +*************************************************************************/ +PtrSFsdCCB SFsdAllocateCCB( +void) +{ + PtrSFsdCCB PtrCCB = NULL; + BOOLEAN AllocatedFromZone = TRUE; + KIRQL CurrentIrql; + + // first, try to allocate out of the zone + KeAcquireSpinLock(&(SFsdGlobalData.ZoneAllocationSpinLock), &CurrentIrql); + if (!ExIsFullZone(&(SFsdGlobalData.CCBZoneHeader))) { + // we have enough memory + PtrCCB = (PtrSFsdCCB)ExAllocateFromZone(&(SFsdGlobalData.CCBZoneHeader)); + + // release the spinlock + KeReleaseSpinLock(&(SFsdGlobalData.ZoneAllocationSpinLock), CurrentIrql); + } else { + // release the spinlock + KeReleaseSpinLock(&(SFsdGlobalData.ZoneAllocationSpinLock), CurrentIrql); + + // if we failed to obtain from the zone, get it directly from the VMM + PtrCCB = (PtrSFsdCCB)ExAllocatePool(NonPagedPool, SFsdQuadAlign(sizeof(SFsdCCB))); + AllocatedFromZone = FALSE; + } + + // if we could not obtain the required memory, bug-check. + // Do NOT do this in your commercial driver, instead handle the error gracefully ... + if (!PtrCCB) { + SFsdPanic(STATUS_INSUFFICIENT_RESOURCES, SFsdQuadAlign(sizeof(SFsdCCB)), 0); + } + + // zero out the allocated memory block + RtlZeroMemory(PtrCCB, SFsdQuadAlign(sizeof(SFsdCCB))); + + // set up some fields ... + PtrCCB->NodeIdentifier.NodeType = SFSD_NODE_TYPE_CCB; + PtrCCB->NodeIdentifier.NodeSize = SFsdQuadAlign(sizeof(SFsdCCB)); + + + if (!AllocatedFromZone) { + SFsdSetFlag(PtrCCB->CCBFlags, SFSD_CCB_NOT_FROM_ZONE); + } + + return(PtrCCB); +} + + +/************************************************************************* +* +* Function: SFsdReleaseCCB() +* +* Description: +* Deallocate a previously allocated structure. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: None +* +*************************************************************************/ +void SFsdReleaseCCB( +PtrSFsdCCB PtrCCB) +{ + KIRQL CurrentIrql; + + ASSERT(PtrCCB); + + // give back memory either to the zone or to the VMM + if (!(PtrCCB->CCBFlags & SFSD_CCB_NOT_FROM_ZONE)) { + // back to the zone + KeAcquireSpinLock(&(SFsdGlobalData.ZoneAllocationSpinLock), &CurrentIrql); + ExFreeToZone(&(SFsdGlobalData.CCBZoneHeader), PtrCCB); + KeReleaseSpinLock(&(SFsdGlobalData.ZoneAllocationSpinLock), CurrentIrql); + } else { + ExFreePool(PtrCCB); + } + + return; +} + + +/************************************************************************* +* +* Function: SFsdAllocateFCB() +* +* Description: +* Allocate a new FCB structure to represent an open on-disk object. +* Also initialize the FCB structure to NULL. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: A pointer to the FCB structure OR NULL. +* +*************************************************************************/ +PtrSFsdFCB SFsdAllocateFCB( +void) +{ + PtrSFsdFCB PtrFCB = NULL; + BOOLEAN AllocatedFromZone = TRUE; + KIRQL CurrentIrql; + + // first, try to allocate out of the zone + KeAcquireSpinLock(&(SFsdGlobalData.ZoneAllocationSpinLock), &CurrentIrql); + if (!ExIsFullZone(&(SFsdGlobalData.FCBZoneHeader))) { + // we have enough memory + PtrFCB = (PtrSFsdFCB)ExAllocateFromZone(&(SFsdGlobalData.FCBZoneHeader)); + + // release the spinlock + KeReleaseSpinLock(&(SFsdGlobalData.ZoneAllocationSpinLock), CurrentIrql); + } else { + // release the spinlock + KeReleaseSpinLock(&(SFsdGlobalData.ZoneAllocationSpinLock), CurrentIrql); + + // if we failed to obtain from the zone, get it directly from the VMM + PtrFCB = (PtrSFsdFCB)ExAllocatePool(NonPagedPool, SFsdQuadAlign(sizeof(SFsdFCB))); + AllocatedFromZone = FALSE; + } + + // if we could not obtain the required memory, bug-check. + // Do NOT do this in your commercial driver, instead handle the error gracefully ... + if (!PtrFCB) { + SFsdPanic(STATUS_INSUFFICIENT_RESOURCES, SFsdQuadAlign(sizeof(SFsdFCB)), 0); + } + + // zero out the allocated memory block + RtlZeroMemory(PtrFCB, SFsdQuadAlign(sizeof(SFsdFCB))); + + // set up some fields ... + PtrFCB->NodeIdentifier.NodeType = SFSD_NODE_TYPE_FCB; + PtrFCB->NodeIdentifier.NodeSize = SFsdQuadAlign(sizeof(SFsdFCB)); + + + if (!AllocatedFromZone) { + SFsdSetFlag(PtrFCB->FCBFlags, SFSD_FCB_NOT_FROM_ZONE); + } + + return(PtrFCB); +} + + + +/************************************************************************* +* +* Function: SFsdCreateNewFCB() +* +* Description: +* We want to create a new FCB. We will also create a new CCB (presumably) +* later. Simply allocate a new FCB structure and initialize fields +* appropriately. +* This function also takes the file size values that the caller must +* have obtained and will set the file size fields appropriately in the +* CommonFCBHeader. +* Finally, this routine will initialize the FileObject structure passed +* in to this function. If you decide to fail the call later, remember +* to uninitialize the fields. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: A pointer to the FCB structure OR NULL. +* +*************************************************************************/ +NTSTATUS SFsdCreateNewFCB( +PtrSFsdFCB *ReturnedFCB, +PLARGE_INTEGER AllocationSize, +PLARGE_INTEGER EndOfFile, +PFILE_OBJECT PtrFileObject, +PtrSFsdVCB PtrVCB) +{ + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdFCB PtrFCB = NULL; + PtrSFsdNTRequiredFCB PtrReqdFCB = NULL; + PFSRTL_COMMON_FCB_HEADER PtrCommonFCBHeader = NULL; + + try { + // Obtain a new FCB structure. + // The function SFsdAllocateFCB() will obtain a new structure either + // from a zone or from memory requested directly from the VMM. + PtrFCB = SFsdAllocateFCB(); + if (!PtrFCB) { + // Assume lack of memory. + try_return(RC = STATUS_INSUFFICIENT_RESOURCES); + } + + // Initialize fields required to interface with the NT Cache Manager. + // Note that the returned structure has already been zeroed. This means + // that the SectionObject structure has been zeroed which is a + // requirement for newly created FCB structures. + PtrReqdFCB = &(PtrFCB->NTRequiredFCB); + + // Initialize the MainResource and PagingIoResource structures now. + ExInitializeResourceLite(&(PtrReqdFCB->MainResource)); + SFsdSetFlag(PtrFCB->FCBFlags, SFSD_INITIALIZED_MAIN_RESOURCE); + + ExInitializeResourceLite(&(PtrReqdFCB->PagingIoResource)); + SFsdSetFlag(PtrFCB->FCBFlags, SFSD_INITIALIZED_PAGING_IO_RESOURCE); + + // Start initializing the fields contained in the CommonFCBHeader. + PtrCommonFCBHeader = &(PtrReqdFCB->CommonFCBHeader); + + // Allow fast-IO for now. + PtrCommonFCBHeader->IsFastIoPossible = FastIoIsPossible; + + // Initialize the MainResource and PagingIoResource pointers in + // the CommonFCBHeader structure to point to the ERESOURCE structures we + // have allocated and already initialized above. + PtrCommonFCBHeader->Resource = &(PtrReqdFCB->MainResource); + PtrCommonFCBHeader->PagingIoResource = &(PtrReqdFCB->PagingIoResource); + + // Ignore the Flags field in the CommonFCBHeader for now. Part 3 + // of the book describes it in greater detail. + + // Initialize the file size values here. + PtrCommonFCBHeader->AllocationSize = *(AllocationSize); + PtrCommonFCBHeader->FileSize = *(EndOfFile); + + // The following will disable ValidDataLength support. However, your + // FSD may choose to support this concept. + PtrCommonFCBHeader->ValidDataLength.LowPart = 0xFFFFFFFF; + PtrCommonFCBHeader->ValidDataLength.HighPart = 0x7FFFFFFF; + + // Initialize other fields for the FCB here ... + PtrFCB->PtrVCB = PtrVCB; + InitializeListHead(&(PtrFCB->NextCCB)); + + // Initialize fields contained in the file object now. + PtrFileObject->PrivateCacheMap = NULL; + // Note that we could have just as well taken the value of PtrReqdFCB + // directly below. The bottom line however is that the FsContext + // field must point to a FSRTL_COMMON_FCB_HEADER structure. + PtrFileObject->FsContext = (void *)(PtrCommonFCBHeader); + + // Other initialization continues here ... + + try_exit: NOTHING; + } finally { + ; + } + + return(RC); +} + + +/************************************************************************* +* +* Function: SFsdReleaseFCB() +* +* Description: +* Deallocate a previously allocated structure. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: None +* +*************************************************************************/ +void SFsdReleaseFCB( +PtrSFsdFCB PtrFCB) +{ + KIRQL CurrentIrql; + + ASSERT(PtrFCB); + + // give back memory either to the zone or to the VMM + if (!(PtrFCB->FCBFlags & SFSD_FCB_NOT_FROM_ZONE)) { + // back to the zone + KeAcquireSpinLock(&(SFsdGlobalData.ZoneAllocationSpinLock), &CurrentIrql); + ExFreeToZone(&(SFsdGlobalData.FCBZoneHeader), PtrFCB); + KeReleaseSpinLock(&(SFsdGlobalData.ZoneAllocationSpinLock), CurrentIrql); + } else { + ExFreePool(PtrFCB); + } + + return; +} + + +/************************************************************************* +* +* Function: SFsdAllocateByteLocks() +* +* Description: +* Allocate a new byte range lock structure and initialize it to NULL. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: A pointer to the SFsdByteLocks structure OR NULL. +* +*************************************************************************/ +PtrSFsdFileLockInfo SFsdAllocateByteLocks( +void) +{ + PtrSFsdFileLockInfo PtrByteLocks = NULL; + BOOLEAN AllocatedFromZone = TRUE; + KIRQL CurrentIrql; + + // first, try to allocate out of the zone + KeAcquireSpinLock(&(SFsdGlobalData.ZoneAllocationSpinLock), &CurrentIrql); + if (!ExIsFullZone(&(SFsdGlobalData.ByteLockZoneHeader))) { + // we have enough memory + PtrByteLocks = (PtrSFsdFileLockInfo)ExAllocateFromZone(&(SFsdGlobalData.ByteLockZoneHeader)); + + // release the spinlock + KeReleaseSpinLock(&(SFsdGlobalData.ZoneAllocationSpinLock), CurrentIrql); + } else { + // release the spinlock + KeReleaseSpinLock(&(SFsdGlobalData.ZoneAllocationSpinLock), CurrentIrql); + + // if we failed to obtain from the zone, get it directly from the VMM + PtrByteLocks = (PtrSFsdFileLockInfo)ExAllocatePool(NonPagedPool, SFsdQuadAlign(sizeof(SFsdFileLockInfo))); + AllocatedFromZone = FALSE; + } + + // if we could not obtain the required memory, bug-check. + // Do NOT do this in your commercial driver, instead handle the error gracefully ... + if (!PtrByteLocks) { + SFsdPanic(STATUS_INSUFFICIENT_RESOURCES, SFsdQuadAlign(sizeof(SFsdFileLockInfo)), 0); + } + + // zero out the allocated memory block + RtlZeroMemory(PtrByteLocks, SFsdQuadAlign(sizeof(PtrSFsdFileLockInfo))); + + if (!AllocatedFromZone) { + SFsdSetFlag(PtrByteLocks->FileLockFlags, SFSD_BYTE_LOCK_NOT_FROM_ZONE); + } + + return(PtrByteLocks); +} + + +/************************************************************************* +* +* Function: SFsdReleaseByteLocks() +* +* Description: +* Deallocate a previously allocated structure. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: None +* +*************************************************************************/ +void SFsdReleaseByteLocks( +PtrSFsdFileLockInfo PtrByteLocks) +{ + KIRQL CurrentIrql; + + ASSERT(PtrByteLocks); + + // give back memory either to the zone or to the VMM + if (!(PtrByteLocks->FileLockFlags & SFSD_BYTE_LOCK_NOT_FROM_ZONE)) { + // back to the zone + KeAcquireSpinLock(&(SFsdGlobalData.ZoneAllocationSpinLock), &CurrentIrql); + ExFreeToZone(&(SFsdGlobalData.ByteLockZoneHeader), PtrByteLocks); + KeReleaseSpinLock(&(SFsdGlobalData.ZoneAllocationSpinLock), CurrentIrql); + } else { + ExFreePool(PtrByteLocks); + } + + return; +} + + +/************************************************************************* +* +* Function: SFsdAllocateIrpContext() +* +* Description: +* The sample FSD creates an IRP context for each request received. This +* routine simply allocates (and initializes to NULL) a SFsdIrpContext +* structure. +* Most of the fields in the context structure are then initialized here. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: A pointer to the IrpContext structure OR NULL. +* +*************************************************************************/ +PtrSFsdIrpContext SFsdAllocateIrpContext( +PIRP Irp, +PDEVICE_OBJECT PtrTargetDeviceObject) +{ + PtrSFsdIrpContext PtrIrpContext = NULL; + BOOLEAN AllocatedFromZone = TRUE; + KIRQL CurrentIrql; + PIO_STACK_LOCATION PtrIoStackLocation = NULL; + + // first, try to allocate out of the zone + KeAcquireSpinLock(&(SFsdGlobalData.ZoneAllocationSpinLock), &CurrentIrql); + if (!ExIsFullZone(&(SFsdGlobalData.IrpContextZoneHeader))) { + // we have enough memory + PtrIrpContext = (PtrSFsdIrpContext)ExAllocateFromZone(&(SFsdGlobalData.IrpContextZoneHeader)); + + // release the spinlock + KeReleaseSpinLock(&(SFsdGlobalData.ZoneAllocationSpinLock), CurrentIrql); + } else { + // release the spinlock + KeReleaseSpinLock(&(SFsdGlobalData.ZoneAllocationSpinLock), CurrentIrql); + + // if we failed to obtain from the zone, get it directly from the VMM + PtrIrpContext = (PtrSFsdIrpContext)ExAllocatePool(NonPagedPool, SFsdQuadAlign(sizeof(SFsdIrpContext))); + AllocatedFromZone = FALSE; + } + + // if we could not obtain the required memory, bug-check. + // Do NOT do this in your commercial driver, instead handle the error gracefully ... + if (!PtrIrpContext) { + SFsdPanic(STATUS_INSUFFICIENT_RESOURCES, SFsdQuadAlign(sizeof(SFsdIrpContext)), 0); + } + + // zero out the allocated memory block + RtlZeroMemory(PtrIrpContext, SFsdQuadAlign(sizeof(SFsdIrpContext))); + + // set up some fields ... + PtrIrpContext->NodeIdentifier.NodeType = SFSD_NODE_TYPE_IRP_CONTEXT; + PtrIrpContext->NodeIdentifier.NodeSize = SFsdQuadAlign(sizeof(SFsdIrpContext)); + + + PtrIrpContext->Irp = Irp; + PtrIrpContext->TargetDeviceObject = PtrTargetDeviceObject; + + // copy over some fields from the IRP and set appropriate flag values + if (Irp) { + PtrIoStackLocation = IoGetCurrentIrpStackLocation(Irp); + ASSERT(PtrIoStackLocation); + + PtrIrpContext->MajorFunction = PtrIoStackLocation->MajorFunction; + PtrIrpContext->MinorFunction = PtrIoStackLocation->MinorFunction; + + // Often, a FSD cannot honor a request for asynchronous processing + // of certain critical requests. For example, a "close" request on + // a file object can typically never be deferred. Therefore, do not + // be surprised if sometimes your FSD (just like all other FSD + // implementations on the Windows NT system) has to override the flag + // below. + if (IoIsOperationSynchronous(Irp)) { + SFsdSetFlag(PtrIrpContext->IrpContextFlags, SFSD_IRP_CONTEXT_CAN_BLOCK); + } + } + + if (!AllocatedFromZone) { + SFsdSetFlag(PtrIrpContext->IrpContextFlags, SFSD_IRP_CONTEXT_NOT_FROM_ZONE); + } + + // Are we top-level ? This information is used by the dispatching code + // later (and also by the FSD dispatch routine) + if (IoGetTopLevelIrp() != Irp) { + // We are not top-level. Note this fact in the context structure + SFsdSetFlag(PtrIrpContext->IrpContextFlags, SFSD_IRP_CONTEXT_NOT_TOP_LEVEL); + } + + return(PtrIrpContext); +} + + +/************************************************************************* +* +* Function: SFsdReleaseIrpContext() +* +* Description: +* Deallocate a previously allocated structure. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: None +* +*************************************************************************/ +void SFsdReleaseIrpContext( +PtrSFsdIrpContext PtrIrpContext) +{ + KIRQL CurrentIrql; + + ASSERT(PtrIrpContext); + + // give back memory either to the zone or to the VMM + if (!(PtrIrpContext->IrpContextFlags & SFSD_IRP_CONTEXT_NOT_FROM_ZONE)) { + // back to the zone + KeAcquireSpinLock(&(SFsdGlobalData.ZoneAllocationSpinLock), &CurrentIrql); + ExFreeToZone(&(SFsdGlobalData.IrpContextZoneHeader), PtrIrpContext); + KeReleaseSpinLock(&(SFsdGlobalData.ZoneAllocationSpinLock), CurrentIrql); + } else { + ExFreePool(PtrIrpContext); + } + + return; +} + + +/************************************************************************* +* +* Function: SFsdPostRequest() +* +* Description: +* Queue up a request for deferred processing (in the context of a system +* worker thread). The caller must have locked the user buffer (if required) +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_PENDING +* +*************************************************************************/ +NTSTATUS SFsdPostRequest( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp) +{ + NTSTATUS RC = STATUS_PENDING; + + // mark the IRP pending + IoMarkIrpPending(PtrIrp); + + // queue up the request + ExInitializeWorkItem(&(PtrIrpContext->WorkQueueItem), SFsdCommonDispatch, PtrIrpContext); + + ExQueueWorkItem(&(PtrIrpContext->WorkQueueItem), CriticalWorkQueue); + + // return status pending + return(RC); +} + + +/************************************************************************* +* +* Function: SFsdCommonDispatch() +* +* Description: +* The common dispatch routine invoked in the context of a system worker +* thread. All we do here is pretty much case off the major function +* code and invoke the appropriate FSD dispatch routine for further +* processing. +* +* Expected Interrupt Level (for execution) : +* +* IRQL PASSIVE_LEVEL +* +* Return Value: None +* +*************************************************************************/ +void SFsdCommonDispatch( +void *Context) // actually an IRPContext structure +{ + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdIrpContext PtrIrpContext = NULL; + PIRP PtrIrp = NULL; + + // The context must be a pointer to an IrpContext structure + PtrIrpContext = (PtrSFsdIrpContext)Context; + ASSERT(PtrIrpContext); + + // Assert that the Context is legitimate + if ((PtrIrpContext->NodeIdentifier.NodeType != SFSD_NODE_TYPE_IRP_CONTEXT) || (PtrIrpContext->NodeIdentifier.NodeSize != SFsdQuadAlign(sizeof(SFsdIrpContext)))) { + // This does not look good + SFsdPanic(SFSD_ERROR_INTERNAL_ERROR, PtrIrpContext->NodeIdentifier.NodeType, PtrIrpContext->NodeIdentifier.NodeSize); + } + + // Get a pointer to the IRP structure + PtrIrp = PtrIrpContext->Irp; + ASSERT(PtrIrp); + + // Now, check if the FSD was top level when the IRP was originally invoked + // and set the thread context (for the worker thread) appropriately + if (PtrIrpContext->IrpContextFlags & SFSD_IRP_CONTEXT_NOT_TOP_LEVEL) { + // The FSD is not top level for the original request + // Set a constant value in TLS to reflect this fact + IoSetTopLevelIrp((PIRP)FSRTL_FSP_TOP_LEVEL_IRP); + } + + // Since the FSD routine will now be invoked in the context of this worker + // thread, we should inform the FSD that it is perfectly OK to block in + // the context of this thread + SFsdSetFlag(PtrIrpContext->IrpContextFlags, SFSD_IRP_CONTEXT_CAN_BLOCK); + + FsRtlEnterFileSystem(); + + try { + + // Pre-processing has been completed; check the Major Function code value + // either in the IrpContext (copied from the IRP), or directly from the + // IRP itself (we will need a pointer to the stack location to do that), + // Then, switch based on the value on the Major Function code + switch (PtrIrpContext->MajorFunction) { + case IRP_MJ_CREATE: + // Invoke the common create routine + (void)SFsdCommonCreate(PtrIrpContext, PtrIrp); + break; + case IRP_MJ_READ: + // Invoke the common read routine + (void)SFsdCommonRead(PtrIrpContext, PtrIrp); + break; + // Continue with the remaining possible dispatch routines below ... + default: + // This is the case where we have an invalid major function + PtrIrp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST; + PtrIrp->IoStatus.Information = 0; + + IoCompleteRequest(PtrIrp, IO_NO_INCREMENT); + break; + } + } except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) { + + RC = SFsdExceptionHandler(PtrIrpContext, PtrIrp); + + SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC); + } + + // Enable preemption + FsRtlExitFileSystem(); + + // Ensure that the "top-level" field is cleared + IoSetTopLevelIrp(NULL); + + return; +} + + +/************************************************************************* +* +* Function: SFsdInitializeVCB() +* +* Description: +* Perform the initialization for a VCB structure. +* +* Expected Interrupt Level (for execution) : +* +* IRQL PASSIVE_LEVEL +* +* Return Value: None +* +*************************************************************************/ +void SFsdInitializeVCB( +PDEVICE_OBJECT PtrVolumeDeviceObject, +PDEVICE_OBJECT PtrTargetDeviceObject, +PVPB PtrVPB) +{ + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdVCB PtrVCB = NULL; + BOOLEAN VCBResourceInitialized = FALSE; + + PtrVCB = (PtrSFsdVCB)(PtrVolumeDeviceObject->DeviceExtension); + + // Zero it out (typically this has already been done by the I/O + // Manager but it does not hurt to do it again)! + RtlZeroMemory(PtrVCB, sizeof(SFsdVCB)); + + // Initialize the signature fields + PtrVCB->NodeIdentifier.NodeType = SFSD_NODE_TYPE_VCB; + PtrVCB->NodeIdentifier.NodeSize = sizeof(SFsdVCB); + + // Initialize the ERESOURCE object. + RC = ExInitializeResourceLite(&(PtrVCB->VCBResource)); + ASSERT(NT_SUCCESS(RC)); + VCBResourceInitialized = TRUE; + + // We know the target device object. + // Note that this is not neccessarily a pointer to the actual + // physical/virtual device on which the logical volume should + // be mounted. This is actually a pointer to either the actual + // (real) device or to any device object that may have been + // attached to it. Any IRPs that we send down should be sent to this + // device object. However, the "real" physical/virtual device object + // on which we perform our mount operation can be determined from the + // RealDevice field in the VPB sent to us. + PtrVCB->TargetDeviceObject = PtrTargetDeviceObject; + + // We also have a pointer to the newly created device object representing + // this logical volume (remember that this VCB structure is simply an + // extension of the created device object). + PtrVCB->VCBDeviceObject = PtrVolumeDeviceObject; + + // We also have the VPB pointer. This was obtained from the + // Parameters.MountVolume.Vpb field in the current I/O stack location + // for the mount IRP. + PtrVCB->PtrVPB = PtrVPB; + + // Initialize the list anchor (head) for some lists in this VCB. + InitializeListHead(&(PtrVCB->NextFCB)); + InitializeListHead(&(PtrVCB->NextNotifyIRP)); + InitializeListHead(&(PtrVCB->VolumeOpenListHead)); + + // Initialize the notify IRP list mutex + KeInitializeMutex(&(PtrVCB->NotifyIRPMutex), 0); + + // Set the initial file size values appropriately. Note that your FSD may + // wish to guess at the initial amount of information you would like to + // read from the disk until you have really determined that this a valid + // logical volume (on disk) that you wish to mount. + // PtrVCB->FileSize = PtrVCB->AllocationSize = ?? + + // You typically do not want to bother with valid data length callbacks + // from the Cache Manager for the file stream opened for volume metadata + // information + PtrVCB->ValidDataLength.LowPart = 0xFFFFFFFF; + PtrVCB->ValidDataLength.HighPart = 0x7FFFFFFF; + + // Create a stream file object for this volume. + PtrVCB->PtrStreamFileObject = IoCreateStreamFileObject(NULL, + PtrVCB->PtrVPB->RealDevice); + ASSERT(PtrVCB->PtrStreamFileObject); + + // Initialize some important fields in the newly created file object. + PtrVCB->PtrStreamFileObject->FsContext = (void *)PtrVCB; + PtrVCB->PtrStreamFileObject->FsContext2 = NULL; + PtrVCB->PtrStreamFileObject->SectionObjectPointer = &(PtrVCB->SectionObject); + + PtrVCB->PtrStreamFileObject->Vpb = PtrVPB; + + // Link this chap onto the global linked list of all VCB structures. + ExAcquireResourceExclusiveLite(&(SFsdGlobalData.GlobalDataResource), TRUE); + InsertTailList(&(SFsdGlobalData.NextVCB), &(PtrVCB->NextVCB)); + + // Initialize caching for the stream file object. + CcInitializeCacheMap(PtrVCB->PtrStreamFileObject, (PCC_FILE_SIZES)(&(PtrVCB->AllocationSize)), + TRUE, // We will use pinned access. + &(SFsdGlobalData.CacheMgrCallBacks), PtrVCB); + + SFsdReleaseResource(&(SFsdGlobalData.GlobalDataResource)); + + // Mark the fact that this VCB structure is initialized. + SFsdSetFlag(PtrVCB->VCBFlags, SFSD_VCB_FLAGS_VCB_INITIALIZED); + + return; +} + + + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/src/read.c`: + +```c +/************************************************************************* +* +* File: read.c +* +* Module: Sample File System Driver (Kernel mode execution only) +* +* Description: +* Contains code to handle the "Read" dispatch entry point. +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +#include "sfsd.h" + +// define the file specific bug-check id +#define SFSD_BUG_CHECK_ID SFSD_FILE_READ + + + +/************************************************************************* +* +* Function: SFsdRead() +* +* Description: +* The I/O Manager will invoke this routine to handle a read +* request +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL (invocation at higher IRQL will cause execution +* to be deferred to a worker thread context) +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFsdRead( +PDEVICE_OBJECT DeviceObject, // the logical volume device object +PIRP Irp) // I/O Request Packet +{ + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdIrpContext PtrIrpContext = NULL; + BOOLEAN AreWeTopLevel = FALSE; + + FsRtlEnterFileSystem(); + ASSERT(DeviceObject); + ASSERT(Irp); + + // set the top level context + AreWeTopLevel = SFsdIsIrpTopLevel(Irp); + + try { + + // get an IRP context structure and issue the request + PtrIrpContext = SFsdAllocateIrpContext(Irp, DeviceObject); + ASSERT(PtrIrpContext); + + RC = SFsdCommonRead(PtrIrpContext, Irp); + + } except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) { + + RC = SFsdExceptionHandler(PtrIrpContext, Irp); + + SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC); + } + + if (AreWeTopLevel) { + IoSetTopLevelIrp(NULL); + } + + FsRtlExitFileSystem(); + + return(RC); +} + + + +/************************************************************************* +* +* Function: SFsdCommonRead() +* +* Description: +* The actual work is performed here. This routine may be invoked in one +* of the two possible contexts: +* (a) in the context of a system worker thread +* (b) in the context of the original caller +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFsdCommonRead( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp) +{ + NTSTATUS RC = STATUS_SUCCESS; + PIO_STACK_LOCATION PtrIoStackLocation = NULL; + LARGE_INTEGER ByteOffset; + uint32 ReadLength = 0, TruncatedReadLength = 0; + uint32 NumberBytesRead = 0; + PFILE_OBJECT PtrFileObject = NULL; + PtrSFsdFCB PtrFCB = NULL; + PtrSFsdCCB PtrCCB = NULL; + PtrSFsdVCB PtrVCB = NULL; + PtrSFsdNTRequiredFCB PtrReqdFCB = NULL; + PERESOURCE PtrResourceAcquired = NULL; + IO_STATUS_BLOCK LocalIoStatus; + void *PtrSystemBuffer = NULL; + uint32 KeyValue = 0; + + BOOLEAN CompleteIrp = TRUE; + BOOLEAN PostRequest = FALSE; + + BOOLEAN CanWait = FALSE; + BOOLEAN PagingIo = FALSE; + BOOLEAN NonBufferedIo = FALSE; + BOOLEAN SynchronousIo = FALSE; + + try { + // First, get a pointer to the current I/O stack location + PtrIoStackLocation = IoGetCurrentIrpStackLocation(PtrIrp); + ASSERT(PtrIoStackLocation); + + // If this happens to be a MDL read complete request, then + // there is not much processing that the FSD has to do. + if (PtrIoStackLocation->MinorFunction & IRP_MN_COMPLETE) { + // Caller wants to tell the Cache Manager that a previously + // allocated MDL can be freed. + SFsdMdlComplete(PtrIrpContext, PtrIrp, PtrIoStackLocation, TRUE); + // The IRP has been completed. + CompleteIrp = FALSE; + try_return(RC = STATUS_SUCCESS); + } + + // If this is a request at IRQL DISPATCH_LEVEL, then post + // the request (your FSD may choose to process it synchronously + // if you implement the support correctly; obviously you will be + // quite constrained in what you can do at such IRQL). + if (PtrIoStackLocation->MinorFunction & IRP_MN_DPC) { + CompleteIrp = FALSE; + PostRequest = TRUE; + try_return(RC = STATUS_PENDING); + } + + PtrFileObject = PtrIoStackLocation->FileObject; + ASSERT(PtrFileObject); + + // Get the FCB and CCB pointers + PtrCCB = (PtrSFsdCCB)(PtrFileObject->FsContext2); + ASSERT(PtrCCB); + PtrFCB = PtrCCB->PtrFCB; + ASSERT(PtrFCB); + + // Get some of the parameters supplied to us + ByteOffset = PtrIoStackLocation->Parameters.Read.ByteOffset; + ReadLength = PtrIoStackLocation->Parameters.Read.Length; + + CanWait = ((PtrIrpContext->IrpContextFlags & SFSD_IRP_CONTEXT_CAN_BLOCK) ? TRUE : FALSE); + PagingIo = ((PtrIrp->Flags & IRP_PAGING_IO) ? TRUE : FALSE); + NonBufferedIo = ((PtrIrp->Flags & IRP_NOCACHE) ? TRUE : FALSE); + SynchronousIo = ((PtrFileObject->Flags & FO_SYNCHRONOUS_IO) ? TRUE : FALSE); + + if (ReadLength == 0) { + // a 0 byte read can be immediately succeeded + try_return(RC); + } + + // NOTE: if your FSD does not support file sizes > 2 GB, you + // could validate the start offset here and return end-of-file + // if the offset begins beyond the maximum supported length. + + // Is this a read of the volume itself ? + if (PtrFCB->NodeIdentifier.NodeType == SFSD_NODE_TYPE_VCB) { + // Yup, we need to send this on to the disk driver after + // validation of the offset and length. + PtrVCB = (PtrSFsdVCB)(PtrFCB); + + // Acquire the volume resource shared ... + if (!ExAcquireResourceSharedLite(&(PtrVCB->VCBResource), CanWait)) { + // post the request to be processed in the context of a worker thread + CompleteIrp = FALSE; + PostRequest = TRUE; + try_return(RC = STATUS_PENDING); + } + PtrResourceAcquired = &(PtrVCB->VCBResource); + + // Insert code to validate the caller supplied offset here + + // Lock the callers buffer + if (!NT_SUCCESS(RC = SFsdLockCallersBuffer(PtrIrp, TRUE, ReadLength))) { + try_return(RC); + } + + // Forward the request to the lower level driver + // For synchronous I/O wait here, else return STATUS_PENDING + // For asynchronous I/O support, read the discussion in Chapter 10 + + try_return(RC); + } + + // If the read request is directed to a page file (if your FSD + // supports paging files), send the request directly to the disk + // driver. For requests directed to a page file, you have to trust + // that the offsets will be set correctly by the VMM. You should not + // attempt to acquire any FSD resources either. + if (PtrFCB->FCBFlags & SFSD_FCB_PAGE_FILE) { + IoMarkIrpPending(PtrIrp); + // You will need to set a completion routine before invoking + // a lower level driver + // forward request directly to disk driver + // SFsdPageFileIo(PtrIrpContext, PtrIrp); + + CompleteIrp = FALSE; + + try_return(RC = STATUS_PENDING); + } + + + // If this read is directed to a directory, it is not allowed + // by the sample FSD. Note that you may choose to create a stream + // file for FSD (internal) directory read/write operations in which + // case you should modify the check below to allow reading (directly + // from disk) directories as long as the read originated from within your FSD + if (PtrFCB->FCBFlags & SFSD_FCB_DIRECTORY) { + RC = STATUS_INVALID_DEVICE_REQUEST; + try_return(RC); + } + + PtrReqdFCB = &(PtrFCB->NTRequiredFCB); + + // This is a good place for oplock related processing. + // Chapter 11 expands upon this topic in greater detail. + + // Check whether the desired read can be allowed depending + // on any byte range locks that might exist. Note that for + // paging-io, no such checks should be performed. + if (!PagingIo) { + // Insert code to perform the check here ... + // if (!SFsdCheckForByteLock(PtrFCB, PtrCCB, PtrIrp, PtrCurrentIoStackLocation)) { + // try_return(RC = STATUS_FILE_LOCK_CONFLICT); + // } + } + + // There are certain complications that arise when the same file stream + // has been opened for cached and non-cached access. The FSD is then + // responsible for maintaining a consistent view of the data seen by + // the caller. + // Also, it is possible for file streams to be mapped in both as data files + // and as an executable. This could also lead to consistency problems since + // there now exist two separate sections (and pages) containing file + // information. + // Read Chapter 10 for more information on the issues involved in + // maintaining data consistency. + // The test below flushes the data cached in system memory if the current + // request madates non-cached access (file stream must be cached) and + // (a) the current request is not paging-io which indicates it is not + // a recursive I/O operation OR originating in the Cache Manager + // (b) OR the current request is paging-io BUT it did not originate via + // the Cache Manager (or is a recursive I/O operation) and we do + // have an image section that has been initialized. +#define SFSD_REQ_NOT_VIA_CACHE_MGR(ptr) (!MmIsRecursiveIoFault() && ((ptr)->ImageSectionObject != NULL)) + + if (NonBufferedIo && (PtrReqdFCB->SectionObject.DataSectionObject != NULL)) { + if (!PagingIo || (SFSD_REQ_NOT_VIA_CACHE_MGR(&(PtrReqdFCB->SectionObject)))) { + CcFlushCache(&(PtrReqdFCB->SectionObject), &ByteOffset, ReadLength, &(PtrIrp->IoStatus)); + // If the flush failed, return error to the caller + if (!NT_SUCCESS(RC = PtrIrp->IoStatus.Status)) { + try_return(RC); + } + } + } + + // Acquire the appropriate FCB resource shared + if (PagingIo) { + // Try to acquire the FCB PagingIoResource shared + if (!ExAcquireResourceSharedLite(&(PtrReqdFCB->PagingIoResource), CanWait)) { + CompleteIrp = FALSE; + PostRequest = TRUE; + try_return(RC = STATUS_PENDING); + } + // Remember the resource that was acquired + PtrResourceAcquired = &(PtrReqdFCB->PagingIoResource); + } else { + // Try to acquire the FCB MainResource shared + if (!ExAcquireResourceSharedLite(&(PtrReqdFCB->MainResource), CanWait)) { + CompleteIrp = FALSE; + PostRequest = TRUE; + try_return(RC = STATUS_PENDING); + } + // Remember the resource that was acquired + PtrResourceAcquired = &(PtrReqdFCB->MainResource); + } + + // Validate start offset and length supplied. + // If start offset is > end-of-file, return an appropriate error. Note + // that since a FCB resource has already been acquired, and since all + // file size changes require acquisition of both FCB resources (see + // Chapter 10), the contents of the FCB and associated data structures + // can safely be examined. + + // Also note that I am using the file size in the "Common FCB Header" + // to perform the check. However, your FSD might decide to keep a + // separate copy in the FCB (or some other representation of the + // file associated with the FCB). + if (RtlLargeIntegerGreaterThan(ByteOffset, PtrReqdFCB->CommonFCBHeader.FileSize)) { + // Starting offset is > file size + try_return(RC = STATUS_END_OF_FILE); + } + + // We can also go ahead and truncate the read length here + // such that it is contained within the file size + + // This is also a good place to set whether fast-io can be performed + // on this particular file or not. Your FSD must make it's own + // determination on whether or not to allow fast-io operations. + // Commonly, fast-io is not allowed if any byte range locks exist + // on the file or if oplocks prevent fast-io. Practically any reason + // choosen by your FSD could result in your setting FastIoIsNotPossible + // OR FastIoIsQuestionable instead of FastIoIsPossible. + // + // PtrReqdFCB->CommonFCBHeader.IsFastIoPossible = FastIoIsPossible; + + + // Branch here for cached vs non-cached I/O + if (!NonBufferedIo) { + + // The caller wishes to perform cached I/O. Initiate caching if + // this is the first cached I/O operation using this file object + if (PtrFileObject->PrivateCacheMap == NULL) { + // This is the first cached I/O operation. You must ensure + // that the FCB Common FCB Header contains valid sizes at this time + CcInitializeCacheMap(PtrFileObject, (PCC_FILE_SIZES)(&(PtrReqdFCB->CommonFCBHeader.AllocationSize)), + FALSE, // We will not utilize pin access for this file + &(SFsdGlobalData.CacheMgrCallBacks), // callbacks + PtrCCB); // The context used in callbacks + } + + // Check and see if this request requires a MDL returned to the caller + if (PtrIoStackLocation->MinorFunction & IRP_MN_MDL) { + // Caller does want a MDL returned. Note that this mode + // implies that the caller is prepared to block + CcMdlRead(PtrFileObject, &ByteOffset, TruncatedReadLength, &(PtrIrp->MdlAddress), &(PtrIrp->IoStatus)); + NumberBytesRead = PtrIrp->IoStatus.Information; + RC = PtrIrp->IoStatus.Status; + + try_return(RC); + } + + // This is a regular run-of-the-mill cached I/O request. Let the + // Cache Manager worry about it! + // First though, we need a buffer pointer (address) that is valid + // More on this in Chapter 10 + PtrSystemBuffer = SFsdGetCallersBuffer(PtrIrp); + ASSERT(PtrSystemBuffer); + if (!CcCopyRead(PtrFileObject, &(ByteOffset), ReadLength, CanWait, PtrSystemBuffer, &(PtrIrp->IoStatus))) { + // The caller was not prepared to block and data is not immediately + // available in the system cache + CompleteIrp = FALSE; + PostRequest = TRUE; + // Mark Irp Pending ... + try_return(RC = STATUS_PENDING); + } + + // We have the data + RC = PtrIrp->IoStatus.Status; + NumberBytesRead = PtrIrp->IoStatus.Information; + + try_return(RC); + + } else { + + // Send the request to lower level drivers + + // For paging-io, the FSD has to trust the VMM to do the right thing + + // Here is a common method used by Windows NT native file systems + // that are in the process of sending a request to the disk driver. + // First, mark the IRP as pending, then invoke the lower level driver + // after setting a completion routine. + // Meanwhile, this particular thread can immediately return a + // STATUS_PENDING return code. + // The completion routine is then responsible for completing the IRP + // and unlocking appropriate resources + + // Also, at this point, your FSD might choose to utilize the + // information contained in the ValidDataLength field to simply + // return zeroes to the caller for reads extending beyond current + // valid data length. + + IoMarkIrpPending(PtrIrp); + + // Invoke a routine to read disk information at this time + // You will need to set a completion routine before invoking + // a lower level driver + + CompleteIrp = FALSE; + + try_return(RC = STATUS_PENDING); + } + + try_exit: NOTHING; + + } finally { + // Post IRP if required + if (PostRequest) { + + // Release any resources acquired here ... + if (PtrResourceAcquired) { + SFsdReleaseResource(PtrResourceAcquired); + } + + // Implement a routine that will queue up the request to be executed + // later (asynchronously) in the context of a system worker thread. + // See Chapter 10 for details. + + // Lock the callers buffer here. Then invoke a common routine to + // perform the post operation. + if (!(PtrIoStackLocation->MinorFunction & IRP_MN_MDL)) { + RC = SFsdLockCallersBuffer(PtrIrp, TRUE, ReadLength); + ASSERT(NT_SUCCESS(RC)); + } + + // Perform the post operation which will mark the IRP pending + // and will return STATUS_PENDING back to us + RC = SFsdPostRequest(PtrIrpContext, PtrIrp); + + } else if (CompleteIrp && !(RC == STATUS_PENDING)) { + // For synchronous I/O, the FSD must maintain the current byte offset + // Do not do this however, if I/O is marked as paging-io + if (SynchronousIo && !PagingIo && NT_SUCCESS(RC)) { + PtrFileObject->CurrentByteOffset = RtlLargeIntegerAdd(ByteOffset, RtlConvertUlongToLargeInteger((unsigned long)NumberBytesRead)); + } + + // If the read completed successfully and this was not a paging-io + // operation, set a flag in the CCB that indicates that a read was + // performed and that the file time should be updated at cleanup + if (NT_SUCCESS(RC) && !PagingIo) { + SFsdSetFlag(PtrCCB->CCBFlags, SFSD_CCB_ACCESSED); + } + + if (PtrResourceAcquired) { + SFsdReleaseResource(PtrResourceAcquired); + } + + // Can complete the IRP here if no exception was encountered + if (!(PtrIrpContext->IrpContextFlags & SFSD_IRP_CONTEXT_EXCEPTION)) { + PtrIrp->IoStatus.Status = RC; + PtrIrp->IoStatus.Information = NumberBytesRead; + + // Free up the Irp Context + SFsdReleaseIrpContext(PtrIrpContext); + + // complete the IRP + IoCompleteRequest(PtrIrp, IO_DISK_INCREMENT); + } + } // can we complete the IRP ? + } // end of "finally" processing + + return(RC); +} + + + + +/************************************************************************* +* +* Function: SFsdGetCallersBuffer() +* +* Description: +* Obtain a pointer to the caller's buffer. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +void *SFsdGetCallersBuffer( +PIRP PtrIrp) +{ + void *ReturnedBuffer = NULL; + + // If an MDL is supplied, use it. + if (PtrIrp->MdlAddress) { + ReturnedBuffer = MmGetSystemAddressForMdl(PtrIrp->MdlAddress); + } else { + ReturnedBuffer = PtrIrp->UserBuffer; + } + + return(ReturnedBuffer); +} + + + +/************************************************************************* +* +* Function: SFsdLockCallersBuffer() +* +* Description: +* Obtain a MDL that describes the buffer. Lock pages for I/O +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFsdLockCallersBuffer( +PIRP PtrIrp, +BOOLEAN IsReadOperation, +uint32 Length) +{ + NTSTATUS RC = STATUS_SUCCESS; + PMDL PtrMdl = NULL; + + ASSERT(PtrIrp); + + try { + // Is a MDL already present in the IRP + if (!(PtrIrp->MdlAddress)) { + // Allocate a MDL + if (!(PtrMdl = IoAllocateMdl(PtrIrp->UserBuffer, Length, FALSE, FALSE, PtrIrp))) { + RC = STATUS_INSUFFICIENT_RESOURCES; + try_return(RC); + } + + // Probe and lock the pages described by the MDL + // We could encounter an exception doing so, swallow the exception + // NOTE: The exception could be due to an unexpected (from our + // perspective), invalidation of the virtual addresses that comprise + // the passed in buffer + try { + MmProbeAndLockPages(PtrMdl, PtrIrp->RequestorMode, (IsReadOperation ? IoWriteAccess:IoReadAccess)); + } except(EXCEPTION_EXECUTE_HANDLER) { + RC = STATUS_INVALID_USER_BUFFER; + } + } + + try_exit: NOTHING; + + } finally { + if (!NT_SUCCESS(RC) && PtrMdl) { + IoFreeMdl(PtrMdl); + // You MUST NULL out the MdlAddress field in the IRP after freeing + // the MDL, else the I/O Manager will also attempt to free the MDL + // pointed to by that field during I/O completion. Obviously, the + // pointer becomes invalid once you free the allocated MDL and hence + // you will encounter a system crash during IRP completion. + PtrIrp->MdlAddress = NULL; + } + } + + return(RC); +} + + + +/************************************************************************* +* +* Function: SFsdMdlComplete() +* +* Description: +* Tell Cache Manager to release MDL (and possibly flush). +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: None. +* +*************************************************************************/ +void SFsdMdlComplete( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp, +PIO_STACK_LOCATION PtrIoStackLocation, +BOOLEAN ReadCompletion) +{ + NTSTATUS RC = STATUS_SUCCESS; + PFILE_OBJECT PtrFileObject = NULL; + + PtrFileObject = PtrIoStackLocation->FileObject; + ASSERT(PtrFileObject); + + // Not much to do here. + if (ReadCompletion) { + CcMdlReadComplete(PtrFileObject, PtrIrp->MdlAddress); + } else { + // The Cache Manager needs the byte offset in the I/O stack location. + CcMdlWriteComplete(PtrFileObject, &(PtrIoStackLocation->Parameters.Write.ByteOffset), PtrIrp->MdlAddress); + } + + // Clear the MDL address field in the IRP so the IoCompleteRequest() + // does not try to play around with the MDL. + PtrIrp->MdlAddress = NULL; + + // Free up the Irp Context. + SFsdReleaseIrpContext(PtrIrpContext); + + // Complete the IRP. + PtrIrp->IoStatus.Status = RC; + PtrIrp->IoStatus.Information = 0; + IoCompleteRequest(PtrIrp, IO_NO_INCREMENT); + + return; +} + + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/src/sfsdinit.c`: + +```c +/************************************************************************* +* +* File: sfsdinit.c +* +* Module: Sample File System Driver (Kernel mode execution only) +* +* Description: +* This file contains the initialization code for the kernel mode +* Sample FSD module. The DriverEntry() routine is called by the I/O +* sub-system to initialize the FSD. +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +#include "sfsd.h" + +// define the file specific bug-check id +#define SFSD_BUG_CHECK_ID SFSD_FILE_INIT + +#define SFSD_FS_NAME L"\\SampleFSD" + +// global variables are declared here +SFsdData SFsdGlobalData; + + +/************************************************************************* +* +* Function: DriverEntry() +* +* Description: +* This routine is the standard entry point for all kernel mode drivers. +* The routine is invoked at IRQL PASSIVE_LEVEL in the context of a +* system worker thread. +* All FSD specific data structures etc. are initialized here. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error (will cause driver to be unloaded). +* +*************************************************************************/ +NTSTATUS DriverEntry( +PDRIVER_OBJECT DriverObject, // created by the I/O sub-system +PUNICODE_STRING RegistryPath) // path to the registry key +{ + NTSTATUS RC = STATUS_SUCCESS; + UNICODE_STRING DriverDeviceName; + BOOLEAN RegisteredShutdown = FALSE; + + SFsdBreakPoint(); + + try { + try { + // initialize the global data structure + RtlZeroMemory(&SFsdGlobalData, sizeof(SFsdGlobalData)); + + // initialize some required fields + SFsdGlobalData.NodeIdentifier.NodeType = SFSD_NODE_TYPE_GLOBAL_DATA; + SFsdGlobalData.NodeIdentifier.NodeSize = sizeof(SFsdGlobalData); + + // initialize the global data resource and remember the fact that + // the resource has been initialized + RC = ExInitializeResourceLite(&(SFsdGlobalData.GlobalDataResource)); + ASSERT(NT_SUCCESS(RC)); + SFsdSetFlag(SFsdGlobalData.SFsdFlags, SFSD_DATA_FLAGS_RESOURCE_INITIALIZED); + + // keep a ptr to the driver object sent to us by the I/O Mgr + SFsdGlobalData.SFsdDriverObject = DriverObject; + + // initialize the mounted logical volume list head + InitializeListHead(&(SFsdGlobalData.NextVCB)); + + // before we proceed with any more initialization, read in + // user supplied configurable values ... + // if (!NT_SUCCESS(RC = SFsdObtainRegistryValues(RegistryPath))) { + // in your commercial driver implementation, it would be + // advisable for your driver to print an appropriate error + // message to the system error log before leaving + // try_return(RC); + // } + + // we should have the registry data (if any), allocate zone memory ... + // This is an example of when FSD implementations try to pre-allocate + // some fixed amount of memory to avoid internal fragmentation and/or waiting + // later during run-time ... + if (!NT_SUCCESS(RC = SFsdInitializeZones())) { + // we failed, print a message and leave ... + try_return(RC); + } + + // initialize the IRP major function table, and the fast I/O table + SFsdInitializeFunctionPointers(DriverObject); + + // create a device object representing the driver itself + // so that requests can be targeted to the driver ... + // e.g. for a disk-based FSD, "mount" requests will be sent to + // this device object by the I/O Manager.\ + // For a redirector/server, you may have applications + // send "special" IOCTL's using this device object ... + RtlInitUnicodeString(&DriverDeviceName, SFSD_FS_NAME); + if (!NT_SUCCESS(RC = IoCreateDevice( + DriverObject, // our driver object + 0, // don't need an extension for this object + &DriverDeviceName,// name - can be used to "open" the driver + // see the book for alternate choices + FILE_DEVICE_DISK_FILE_SYSTEM, + 0, // no special characteristics + // do not want this as an exclusive device, though you might + FALSE, + &(SFsdGlobalData.SFsdDeviceObject)))) { + // failed to create a device object, leave ... + try_return(RC); + } + +#ifdef _THIS_IS_A_NETWORK_REDIR_OR_SERVER_ + + // since network redirectors/servers do not register themselves as + // "file systems", the I/O Manager does not ordinarily request the + // FSD to flush logical volumes at shutdown. To get some notification + // at shutdown, use the IoRegisterShutdownNotification() instead ... + if (!NT_SUCCESS(RC = IoRegisterShutdownNotification(SFsdGlobalData.SFsdDeviceObject))) { + // failed to register shutdown notification ... + try_return(RC); + } + RegisteredShutdown = TRUE; + + // Register the network FSD with the MUP component. + if (!NT_SUCCESS(RC = FsRtlRegisterUncProvider(&(SFsdGlobalData.MupHandle), &DriverDeviceName, FALSE))) { + try_return(RC); + } + +#else // This is a disk based FSD + + // register the driver with the I/O Manager, pretend as if this is + // a physical disk based FSD (or in order words, this FSD manages + // logical volumes residing on physical disk drives) + IoRegisterFileSystem(SFsdGlobalData.SFsdDeviceObject); + +#endif // _THIS_IS_A_NETWORK_REDIR_OR_SERVER_ + + } except (EXCEPTION_EXECUTE_HANDLER) { + // we encountered an exception somewhere, eat it up + RC = GetExceptionCode(); + } + + try_exit: NOTHING; + } finally { + // start unwinding if we were unsuccessful + if (!NT_SUCCESS(RC)) { +#ifdef _THIS_IS_A_NETWORK_REDIR_OR_SERVER_ + if (RegisteredShutdown) { + IoUnregisterShutdownNotification(SFsdGlobalData.SFsdDeviceObject); + } +#endif // _THIS_IS_A_NETWORK_REDIR_OR_SERVER_ + + // Now, delete any device objects, etc. we may have created + if (SFsdGlobalData.SFsdDeviceObject) { + IoDeleteDevice(SFsdGlobalData.SFsdDeviceObject); + SFsdGlobalData.SFsdDeviceObject = NULL; + } + + // free up any memory we might have reserved for zones/lookaside + // lists + if (SFsdGlobalData.SFsdFlags & SFSD_DATA_FLAGS_ZONES_INITIALIZED) { + SFsdDestroyZones(); + } + + // delete the resource we may have initialized + if (SFsdGlobalData.SFsdFlags & SFSD_DATA_FLAGS_RESOURCE_INITIALIZED) { + // un-initialize this resource + ExDeleteResourceLite(&(SFsdGlobalData.GlobalDataResource)); + SFsdClearFlag(SFsdGlobalData.SFsdFlags, SFSD_DATA_FLAGS_RESOURCE_INITIALIZED); + } + } + } + + return(RC); +} + + + +/************************************************************************* +* +* Function: SFsdInitializeFunctionPointers() +* +* Description: +* Initialize the IRP... function pointer array in the driver object +* structure. Also initialize the fast-io function ptr array ... +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: None +* +*************************************************************************/ +void SFsdInitializeFunctionPointers( +PDRIVER_OBJECT DriverObject) // created by the I/O sub-system +{ + PFAST_IO_DISPATCH PtrFastIoDispatch = NULL; + + // initialize the function pointers for the IRP major + // functions that this FSD is prepared to handle ... + // NT Version 4.0 has 28 possible functions that a + // kernel mode driver can handle. + // NT Version 3.51 and before has only 22 such functions, + // of which 18 are typically interesting to most FSD's. + + // The only interesting new functions that a FSD might + // want to respond to beginning with Version 4.0 are the + // IRP_MJ_QUERY_QUOTA and the IRP_MJ_SET_QUOTA requests. + + // The code below does not handle quota manipulation, neither + // does the NT Version 4.0 operating system (or I/O Manager). + // However, you should be on the lookout for any such new + // functionality that your FSD might have to implement in + // the near future. + + DriverObject->MajorFunction[IRP_MJ_CREATE] = SFsdCreate; + DriverObject->MajorFunction[IRP_MJ_CLOSE] = SFsdClose; + DriverObject->MajorFunction[IRP_MJ_READ] = SFsdRead; + DriverObject->MajorFunction[IRP_MJ_WRITE] = SFsdWrite; + + DriverObject->MajorFunction[IRP_MJ_QUERY_INFORMATION] = SFsdFileInfo; + DriverObject->MajorFunction[IRP_MJ_SET_INFORMATION] = SFsdFileInfo; + + DriverObject->MajorFunction[IRP_MJ_FLUSH_BUFFERS] = SFsdFlush; + // To implement support for querying and modifying volume attributes + // (volume information query/set operations), enable initialization + // of the following two function pointers and then implement the supporting + // functions. Use Chapter 11 in the text to assist you in your efforts. + // DriverObject->MajorFunction[IRP_MJ_QUERY_VOLUME_INFORMATION] = SFsdVolInfo; + // DriverObject->MajorFunction[IRP_MJ_SET_VOLUME_INFORMATION] = SFsdVolInfo; + DriverObject->MajorFunction[IRP_MJ_DIRECTORY_CONTROL] = SFsdDirControl; + // To implement support for file system IOCTL calls, enable initialization + // of the following function pointer and implement appropriate support. Use + // Chapter 11 in the text to assist you in your efforts. + // DriverObject->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL] = SFsdFSControl; + DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = SFsdDeviceControl; + DriverObject->MajorFunction[IRP_MJ_SHUTDOWN] = SFsdShutdown; + // For byte-range lock support, enable initialization of the following + // function pointer and implement appropriate support. Use Chapter 10 + // in the text to assist you in your efforts. + // DriverObject->MajorFunction[IRP_MJ_LOCK_CONTROL] = SFsdLockControl; + DriverObject->MajorFunction[IRP_MJ_CLEANUP] = SFsdCleanup; + // If your FSD supports security attributes, you should provide appropriate + // dispatch entry points and initialize the function pointers as given below. + // DriverObject->MajorFunction[IRP_MJ_QUERY_SECURITY] = SFsdSecurity; + // DriverObject->MajorFunction[IRP_MJ_SET_SECURITY] = SFsdSecurity; + // If you support extended attributes, you should provide appropriate + // dispatch entry points and initialize the function pointers as given below. + // DriverObject->MajorFunction[IRP_MJ_QUERY_EA] = SFsdExtendedAttr; + // DriverObject->MajorFunction[IRP_MJ_SET_EA] = SFsdExtendedAttr; + + // Now, it is time to initialize the fast-io stuff ... + PtrFastIoDispatch = DriverObject->FastIoDispatch = &(SFsdGlobalData.SFsdFastIoDispatch); + + // initialize the global fast-io structure + // NOTE: The fast-io structure has undergone a substantial revision + // in Windows NT Version 4.0. The structure has been extensively expanded. + // Therefore, if your driver needs to work on both V3.51 and V4.0+, + // you will have to be able to distinguish between the two versions at compile time. + PtrFastIoDispatch->SizeOfFastIoDispatch = sizeof(FAST_IO_DISPATCH); + PtrFastIoDispatch->FastIoCheckIfPossible = SFsdFastIoCheckIfPossible; + PtrFastIoDispatch->FastIoRead = SFsdFastIoRead; + PtrFastIoDispatch->FastIoWrite = SFsdFastIoWrite; + PtrFastIoDispatch->FastIoQueryBasicInfo = SFsdFastIoQueryBasicInfo; + PtrFastIoDispatch->FastIoQueryStandardInfo = SFsdFastIoQueryStdInfo; + PtrFastIoDispatch->FastIoLock = SFsdFastIoLock; + PtrFastIoDispatch->FastIoUnlockSingle = SFsdFastIoUnlockSingle; + PtrFastIoDispatch->FastIoUnlockAll = SFsdFastIoUnlockAll; + PtrFastIoDispatch->FastIoUnlockAllByKey = SFsdFastIoUnlockAllByKey; + PtrFastIoDispatch->AcquireFileForNtCreateSection = SFsdFastIoAcqCreateSec; + PtrFastIoDispatch->ReleaseFileForNtCreateSection = SFsdFastIoRelCreateSec; + + // the remaining are only valid under NT Version 4.0 and later +#if(_WIN32_WINNT >= 0x0400) + PtrFastIoDispatch->FastIoQueryNetworkOpenInfo = SFsdFastIoQueryNetInfo; + PtrFastIoDispatch->AcquireForModWrite = SFsdFastIoAcqModWrite; + PtrFastIoDispatch->ReleaseForModWrite = SFsdFastIoRelModWrite; + PtrFastIoDispatch->AcquireForCcFlush = SFsdFastIoAcqCcFlush; + PtrFastIoDispatch->ReleaseForCcFlush = SFsdFastIoRelCcFlush; + + // MDL functionality + PtrFastIoDispatch->MdlRead = SFsdFastIoMdlRead; + PtrFastIoDispatch->MdlReadComplete = SFsdFastIoMdlReadComplete; + PtrFastIoDispatch->PrepareMdlWrite = SFsdFastIoPrepareMdlWrite; + PtrFastIoDispatch->MdlWriteComplete = SFsdFastIoMdlWriteComplete; + + // although this FSD does not support compressed read/write functionality, + // NTFS does, and if you design a FSD that can provide such functionality, + // you should consider initializing the fast io entry points for reading + // and/or writing compressed data ... +#endif // (_WIN32_WINNT >= 0x0400) + + // last but not least, initialize the Cache Manager callback functions + // which are used in CcInitializeCacheMap() + SFsdGlobalData.CacheMgrCallBacks.AcquireForLazyWrite = SFsdAcqLazyWrite; + SFsdGlobalData.CacheMgrCallBacks.ReleaseFromLazyWrite = SFsdRelLazyWrite; + SFsdGlobalData.CacheMgrCallBacks.AcquireForReadAhead = SFsdAcqReadAhead; + SFsdGlobalData.CacheMgrCallBacks.ReleaseFromReadAhead = SFsdRelReadAhead; + + return; +} + + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/src/shutdown.c`: + +```c +/************************************************************************* +* +* File: shutdown.c +* +* Module: Sample File System Driver (Kernel mode execution only) +* +* Description: +* Contains code to handle the "shutdown notification" dispatch entry point. +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +#include "sfsd.h" + +// define the file specific bug-check id +#define SFSD_BUG_CHECK_ID SFSD_FILE_SHUTDOWN + + + +/************************************************************************* +* +* Function: SFsdShutdown() +* +* Description: +* All disk-based FSDs can expect to receive this shutdown notification +* request whenever the system is about to be halted gracefully. If you +* design and implement a network redirector, you must register explicitly +* for shutdown notification by invoking the IoRegisterShutdownNotification() +* routine from your driver entry. +* +* Note that drivers that register to receive shutdown notification get +* invoked BEFORE disk-based FSDs are told about the shutdown notification. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: Irrelevant. +* +*************************************************************************/ +NTSTATUS SFsdShutdown( +PDEVICE_OBJECT DeviceObject, // the logical volume device object +PIRP Irp) // I/O Request Packet +{ + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdIrpContext PtrIrpContext = NULL; + BOOLEAN AreWeTopLevel = FALSE; + + FsRtlEnterFileSystem(); + ASSERT(DeviceObject); + ASSERT(Irp); + + // set the top level context + AreWeTopLevel = SFsdIsIrpTopLevel(Irp); + + try { + + // get an IRP context structure and issue the request + PtrIrpContext = SFsdAllocateIrpContext(Irp, DeviceObject); + ASSERT(PtrIrpContext); + + RC = SFsdCommonShutdown(PtrIrpContext, Irp); + + } except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) { + + RC = SFsdExceptionHandler(PtrIrpContext, Irp); + + SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC); + } + + if (AreWeTopLevel) { + IoSetTopLevelIrp(NULL); + } + + FsRtlExitFileSystem(); + + return(RC); +} + + + +/************************************************************************* +* +* Function: SFsdCommonShutdown() +* +* Description: +* The actual work is performed here. Basically, all we do here is +* internally invoke a flush on all mounted logical volumes. This, in +* tuen, will result in all open file streams being flushed to disk. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: Irrelevant +* +*************************************************************************/ +NTSTATUS SFsdCommonShutdown( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp) +{ + NTSTATUS RC = STATUS_SUCCESS; + PIO_STACK_LOCATION PtrIoStackLocation = NULL; + IO_STATUS_BLOCK LocalIoStatus; + + try { + // First, get a pointer to the current I/O stack location + PtrIoStackLocation = IoGetCurrentIrpStackLocation(PtrIrp); + ASSERT(PtrIoStackLocation); + + // Here is the algorithm you can follow in implementing your shutdown + // functionality. Note, of course, that it is quite possible that you + // may wish to do much more specialized processing in your specific + // (commercial) FSD at shutdown time; the steps listed below, however, + // are those that you should implement at a minimum: + // (a) Block all new "mount volume" requests by acquiring an appropriate + // global resource/lock. + // (b) Go through your linked list of mounted logical volumes and for + // each such volume, do the following: + // (i) acquire the volume resource exclusively + // (ii) invoke SFsdFlushLogicalVolume() (internally) to flush the + // open data streams belonging to the volume from the system + // cache + // (iii) Invoke the physical/virtual/logical target device object + // on which the volume is mounted and inform this device + // about the shutdown request (Use IoBuildSynchronousFsdRequest() + // to create an IRP with MajorFunction = IRP_MJ_SHUTDOWN that you + // will then issue to the target device object). + // (iv) Wait for the completion of the shutdown processing by the target + // device object + // (v) Release the VCB resource you will have acquired in (i) above. + + // Once you have processed all the mounted logical volumes, you can release + // all acquired global resources and leave (in peace :-) + + try_exit: NOTHING; + + } finally { + + // See the read/write examples for how to fill in this portion + + } // end of "finally" processing + + return(RC); +} + + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/src/sources`: + +``` +# - Execute the "build" command to make the sample FSD driver + +# The TARGETNAME variable is defined by the developer. It is the name of +# the target (component) that is being built by this makefile. It +# should NOT include any path or file extension information. + +TARGETNAME=sfsd + +# The TARGETPATH and TARGETTYPE variables are defined by the developer. +# The first specifies where the target is to be build. The second specifies +# the type of target (either PROGRAM, DYNLINK, LIBRARY, UMAPPL_NOLIB or +# BOOTPGM). UMAPPL_NOLIB is used when you're only building user-mode +# apps and don't need to build a library. + +TARGETPATH=obj + +TARGETTYPE=DRIVER + +# The INCLUDES variable specifies any include paths that are specific to +# this source directory. Separate multiple directory paths with single +# semicolons. Relative path specifications are okay. The INCLUDES +# variable is not required. Specifying an empty INCLUDES variable +# (i.e. INCLUDES= ) indicates no include paths are to be searched. +# +# NOTE: The "fsdk\inc" refers to the Microsoft supplied Installable File Systems +# Developers Kit. +INCLUDES=..\inc;\ddk-40\inc;\fsdk\inc-40; + +# The SOURCES variable is defined by the developer. It is a list of all the +# source files for this component. Each source file should be on a separate +# line using the line continuation character. This will minimize merge +# conflicts if two developers adding source files to the same component. +# The SOURCES variable is required. If there are no platform common source +# files, an empty SOURCES variable should be used. (i.e. SOURCES= ) + +# Source files common to multiple platforms + +SOURCES=sfsdinit.c \ + create.c \ + misc.c \ + cleanup.c \ + close.c \ + read.c \ + write.c \ + fileinfo.c \ + flush.c \ + dircntrl.c \ + devcntrl.c \ + shutdown.c \ + fastio.c + +# Next specify any additional options for the compiler. +# Define the appropriate CPU type (and insert defines +# in the appropriate header file) to get the right +# values for "uint8", "uint16" etc. typedefs. + +C_DEFINES= -DUNICODE -D_CPU_X86_ + +# The type of product being built - NT = kernel mode + +UMTYPE=nt + +``` + +`Windows Driver Development/Windows NT File System Internals/filesys/src/write.c`: + +```c +/************************************************************************* +* +* File: write.c +* +* Module: Sample File System Driver (Kernel mode execution only) +* +* Description: +* Contains code to handle the "Write" dispatch entry point. +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +#include "sfsd.h" + +// define the file specific bug-check id +#define SFSD_BUG_CHECK_ID SFSD_FILE_WRITE + + + +/************************************************************************* +* +* Function: SFsdWrite() +* +* Description: +* The I/O Manager will invoke this routine to handle a write +* request +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL (invocation at higher IRQL will cause execution +* to be deferred to a worker thread context) +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFsdWrite( +PDEVICE_OBJECT DeviceObject, // the logical volume device object +PIRP Irp) // I/O Request Packet +{ + NTSTATUS RC = STATUS_SUCCESS; + PtrSFsdIrpContext PtrIrpContext = NULL; + BOOLEAN AreWeTopLevel = FALSE; + + FsRtlEnterFileSystem(); + ASSERT(DeviceObject); + ASSERT(Irp); + + // set the top level context + AreWeTopLevel = SFsdIsIrpTopLevel(Irp); + + try { + + // get an IRP context structure and issue the request + PtrIrpContext = SFsdAllocateIrpContext(Irp, DeviceObject); + ASSERT(PtrIrpContext); + + RC = SFsdCommonWrite(PtrIrpContext, Irp); + + } except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) { + + RC = SFsdExceptionHandler(PtrIrpContext, Irp); + + SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC); + } + + if (AreWeTopLevel) { + IoSetTopLevelIrp(NULL); + } + + FsRtlExitFileSystem(); + + return(RC); +} + + + +/************************************************************************* +* +* Function: SFsdCommonWrite() +* +* Description: +* The actual work is performed here. This routine may be invoked in one' +* of the two possible contexts: +* (a) in the context of a system worker thread +* (b) in the context of the original caller +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFsdCommonWrite( +PtrSFsdIrpContext PtrIrpContext, +PIRP PtrIrp) +{ + NTSTATUS RC = STATUS_SUCCESS; + PIO_STACK_LOCATION PtrIoStackLocation = NULL; + LARGE_INTEGER ByteOffset; + uint32 WriteLength = 0, TruncatedWriteLength = 0; + uint32 NumberBytesWritten = 0; + PFILE_OBJECT PtrFileObject = NULL; + PtrSFsdFCB PtrFCB = NULL; + PtrSFsdCCB PtrCCB = NULL; + PtrSFsdVCB PtrVCB = NULL; + PtrSFsdNTRequiredFCB PtrReqdFCB = NULL; + PERESOURCE PtrResourceAcquired = NULL; + IO_STATUS_BLOCK LocalIoStatus; + void *PtrSystemBuffer = NULL; + uint32 KeyValue = 0; + + BOOLEAN CompleteIrp = TRUE; + BOOLEAN PostRequest = FALSE; + + BOOLEAN CanWait = FALSE; + BOOLEAN PagingIo = FALSE; + BOOLEAN NonBufferedIo = FALSE; + BOOLEAN SynchronousIo = FALSE; + BOOLEAN IsThisADeferredWrite = FALSE; + BOOLEAN WritingAtEndOfFile = FALSE; + + try { + // First, get a pointer to the current I/O stack location + PtrIoStackLocation = IoGetCurrentIrpStackLocation(PtrIrp); + ASSERT(PtrIoStackLocation); + + PtrFileObject = PtrIoStackLocation->FileObject; + ASSERT(PtrFileObject); + + // If this happens to be a MDL write complete request, then + // allocated MDL can be freed. This may cause a recursive write + // back into the FSD. + if (PtrIoStackLocation->MinorFunction & IRP_MN_COMPLETE) { + // Caller wants to tell the Cache Manager that a previously + // allocated MDL can be freed. + SFsdMdlComplete(PtrIrpContext, PtrIrp, PtrIoStackLocation, FALSE); + // The IRP has been completed. + CompleteIrp = FALSE; + try_return(RC = STATUS_SUCCESS); + } + + // If this is a request at IRQL DISPATCH_LEVEL, then post + // the request (your FSD may choose to process it synchronously + // if you implement the support correctly). + if (PtrIoStackLocation->MinorFunction & IRP_MN_DPC) { + CompleteIrp = FALSE; + PostRequest = TRUE; + try_return(RC = STATUS_PENDING); + } + + // Get the FCB and CCB pointers + PtrCCB = (PtrSFsdCCB)(PtrFileObject->FsContext2); + ASSERT(PtrCCB); + PtrFCB = PtrCCB->PtrFCB; + ASSERT(PtrFCB); + + // Get some of the parameters supplied to us + ByteOffset = PtrIoStackLocation->Parameters.Write.ByteOffset; + WriteLength = PtrIoStackLocation->Parameters.Write.Length; + + CanWait = ((PtrIrpContext->IrpContextFlags & SFSD_IRP_CONTEXT_CAN_BLOCK) ? TRUE : FALSE); + PagingIo = ((PtrIrp->Flags & IRP_PAGING_IO) ? TRUE : FALSE); + NonBufferedIo = ((PtrIrp->Flags & IRP_NOCACHE) ? TRUE : FALSE); + SynchronousIo = ((PtrFileObject->Flags & FO_SYNCHRONOUS_IO) ? TRUE : FALSE); + + // You might wish to check at this point whether the file object being + // used for write really did have write permission requested when the + // create/open operation was performed. Of course, for paging-io write + // operations, the check is not valid since paging-io (via the VMM) could + // use any file object (likely the first one with which caching was + // initiated on the FCB) to perform the write operation + + if (WriteLength == 0) { + // a 0 byte write can be immediately succeeded + try_return(RC); + } + + // NOTE: if your FSD does not support file sizes > 2 GB, you + // could validate the start offset here and return end-of-file + // if the offset begins beyond the maximum supported length. + + // Is this a write of the volume itself ? + if (PtrFCB->NodeIdentifier.NodeType == SFSD_NODE_TYPE_VCB) { + // Yup, we need to send this on to the disk driver after + // validation of the offset and length. + PtrVCB = (PtrSFsdVCB)(PtrFCB); + + // Acquire the volume resource exclusively + if (!ExAcquireResourceSharedLite(&(PtrVCB->VCBResource), CanWait)) { + // post the request to be processed in the context of a worker thread + CompleteIrp = FALSE; + PostRequest = TRUE; + try_return(RC = STATUS_PENDING); + } + PtrResourceAcquired = &(PtrVCB->VCBResource); + + // Insert code to validate the caller supplied offset here + + // Lock the callers buffer + if (!NT_SUCCESS(RC = SFsdLockCallersBuffer(PtrIrp, TRUE, WriteLength))) { + try_return(RC); + } + + // Forward the request to the lower level driver + + // For synchronous I/O wait here, else return STATUS_PENDING + // For asynchronous I/O support, read the discussion in Chapter 10 + + try_return(RC); + } + + // Your FSD (if it is a "nice" FSD) should check whether it is + // convenient to allow the write to proceed by utilizing the + // CcCanIWrite() function call. If it is not convenient to perform + // the write at this time, you should defer the request for a while. + // The check should not however be performed for non-cached write + // operations. To determine whether we are retrying the operation + // or now, use the IrpContext structure we have created (See the + // appendix to this book for a definition of the structure) + IsThisADeferredWrite = ((PtrIrpContext->IrpContextFlags & SFSD_IRP_CONTEXT_DEFERRED_WRITE) ? TRUE : FALSE); + if (!NonBufferedIo) { + if (!CcCanIWrite(PtrFileObject, WriteLength, CanWait, IsThisADeferredWrite)) { + // Cache Manager and/or the VMM does not want us to perform + // the write at this time. Post the request. + SFsdSetFlag(PtrIrpContext->IrpContextFlags, SFSD_IRP_CONTEXT_DEFERRED_WRITE); + CcDeferWrite(PtrFileObject, SFsdDeferredWriteCallBack, PtrIrpContext, PtrIrp, WriteLength, IsThisADeferredWrite); + CompleteIrp = FALSE; + try_return(RC = STATUS_PENDING); + } + } + + // If the write request is directed to a page file (if your FSD + // supports paging files), send the request directly to the disk + // driver. For requests directed to a page file, you have to trust + // that the offsets will be set correctly by the VMM. You should not + // attempt to acquire any FSD resources either. + if (PtrFCB->FCBFlags & SFSD_FCB_PAGE_FILE) { + IoMarkIrpPending(PtrIrp); + // You will need to set a completion routine before invoking a lower level driver. + // Forward request directly to disk driver. + // SFsdPageFileIo(PtrIrpContext, PtrIrp); + + CompleteIrp = FALSE; + + try_return(RC = STATUS_PENDING); + } + + // We can continue. Check whether this write operation is targeted + // to a directory object in which case the sample FSD will disallow + // the write request. Once again though, if you create a stream file + // object to represent a directory in memory, you could come to this + // point as a result of modifying the directory contents internally + // by the FSD itself. In that case, you should be able to differentiate + // the directory write as being an internal, non-cached write operation + // and allow it to proceed. + if (PtrFCB->FCBFlags & SFSD_FCB_DIRECTORY) { + RC = STATUS_INVALID_DEVICE_REQUEST; + try_return(RC); + } + + PtrReqdFCB = &(PtrFCB->NTRequiredFCB); + + // Acquire the appropriate FCB resource exclusively + if (PagingIo) { + // Try to acquire the FCB PagingIoResource exclusively + if (!ExAcquireResourceExclusiveLite(&(PtrReqdFCB->PagingIoResource), CanWait)) { + CompleteIrp = FALSE; + PostRequest = TRUE; + try_return(RC = STATUS_PENDING); + } + // Remember the resource that was acquired + PtrResourceAcquired = &(PtrReqdFCB->PagingIoResource); + } else { + // Try to acquire the FCB MainResource exclusively + if (!ExAcquireResourceExclusiveLite(&(PtrReqdFCB->MainResource), CanWait)) { + CompleteIrp = FALSE; + PostRequest = TRUE; + try_return(RC = STATUS_PENDING); + } + // Remember the resource that was acquired + PtrResourceAcquired = &(PtrReqdFCB->MainResource); + } + + // Validate start offset and length supplied. + // Here is a special check that determines whether the caller wishes to + // begin the write at current end-of-file (whatever the value of that + // offset might be) + if ((ByteOffset.LowPart == FILE_WRITE_TO_END_OF_FILE) && (ByteOffset.HighPart == 0xFFFFFFFF)) { + WritingAtEndOfFile = TRUE; + } + + // There are certain complications that arise when the same file stream + // has been opened for cached and non-cached access. The FSD is then + // responsible for maintaining a consistent view of the data seen by + // the caller. + // If this happens to be a non-buffered I/O, you should try to flush the + // cached data (if some other file object has already initiated caching + // on the file stream). You should also try to purge the cached + // information though the purge will probably fail if the file has been + // mapped into some process' virtual address space + // Read Chapter 10 for more information on the issues involved in + // maintaining data consistency. + if (NonBufferedIo && !PagingIo && (PtrReqdFCB->SectionObject.DataSectionObject != NULL)) { + // Flush and then attempt to purge the cache + CcFlushCache(&(PtrReqdFCB->SectionObject), &ByteOffset, WriteLength, &(PtrIrp->IoStatus)); + // If the flush failed, return error to the caller + if (!NT_SUCCESS(RC = PtrIrp->IoStatus.Status)) { + try_return(RC); + } + + // Attempt the purge and ignore the return code + CcPurgeCacheSection(&(PtrReqdFCB->SectionObject), (WritingAtEndOfFile ? &(PtrReqdFCB->CommonFCBHeader.FileSize) : &(ByteOffset)), + WriteLength, FALSE); + // We are finished with our flushing and purging + } + + // Paging I/O write operations are special. If paging i/o write + // requests begin beyond end-of-file, the request should be no-oped + // (see the next two chapters for more information). If paging i/o + // requests extend beyond current end of file, they should be truncated + // to current end-of-file. + // Insert code to do this here. + + // This is also a good place to set whether fast-io can be performed + // on this particular file or not. Your FSD must make it's own + // determination on whether or not to allow fast-io operations. + // Commonly, fast-io is not allowed if any byte range locks exist + // on the file or if oplocks prevent fast-io. Practically any reason + // choosen by your FSD could result in your setting FastIoIsNotPossible + // OR FastIoIsQuestionable instead of FastIoIsPossible. + // + // PtrReqdFCB->CommonFCBHeader.IsFastIoPossible = FastIoIsPossible; + + // This is a good place for oplock related processing. + // Chapter 11 expands upon this topic in greater detail. + + // Check whether the desired write can be allowed depending + // on any byte range locks that might exist. Note that for + // paging-io, no such checks should be performed. + if (!PagingIo) { + // Insert code to perform the check here ... + // if (!SFsdCheckForByteLock(PtrFCB, PtrCCB, PtrIrp, + // PtrCurrentIoStackLocation)) { + // try_return(RC = STATUS_FILE_LOCK_CONFLICT); + // } + } + + // Check whether the current request will extend the file size, + // or the valid data length (if your FSD supports the concept of a + // valid data length associated with the file stream). In either case, + // inform the Cache Manager at this time using CcSetFileSizes() about + // the new file length. Note that real FSD implementations will have to + // first allocate enough on-disk space at this point (before they + // inform the Cache Manager about the new size) to ensure that the write + // will subsequently not fail due to lack of disk space. + + // if ((WritingAtEndOfFile) || ((ByteOffset + TruncatedWriteLength) > PtrReqdFCB->CommonFCBHeader.FileSize)) { + // we are extending the file; + // allocate space and inform the Cache Manager; + // } else if (same test as above for valid data length) { + // we are extending valid data length, inform Cache Manager; + // } + + + // Branch here for cached vs non-cached I/O + if (!NonBufferedIo) { + + // The caller wishes to perform cached I/O. Initiate caching if + // this is the first cached I/O operation using this file object + if (PtrFileObject->PrivateCacheMap == NULL) { + // This is the first cached I/O operation. You must ensure + // that the FCB Common FCB Header contains valid sizes at this time + CcInitializeCacheMap(PtrFileObject, (PCC_FILE_SIZES)(&(PtrReqdFCB->CommonFCBHeader.AllocationSize)), + FALSE, // We will not utilize pin access for this file + &(SFsdGlobalData.CacheMgrCallBacks), // callbacks + PtrCCB); // The context used in callbacks + } + + // Check and see if this request requires a MDL returned to the caller + if (PtrIoStackLocation->MinorFunction & IRP_MN_MDL) { + // Caller does want a MDL returned. Note that this mode + // implies that the caller is prepared to block + CcPrepareMdlWrite(PtrFileObject, &ByteOffset, TruncatedWriteLength, &(PtrIrp->MdlAddress), &(PtrIrp->IoStatus)); + NumberBytesWritten = PtrIrp->IoStatus.Information; + RC = PtrIrp->IoStatus.Status; + + try_return(RC); + } + + // This is a regular run-of-the-mill cached I/O request. Let the + // Cache Manager worry about it! + // First though, we need a buffer pointer (address) that is valid + // More on this in Chapter 10 + + // Also, if the request extends the ValidDataLength, use CcZeroData() + // first to zero out the gap (if any) between current valid data + // length and the start of the request + PtrSystemBuffer = SFsdGetCallersBuffer(PtrIrp); + ASSERT(PtrSystemBuffer); + if (!CcCopyWrite(PtrFileObject, &(ByteOffset), TruncatedWriteLength, CanWait, PtrSystemBuffer)) { + // The caller was not prepared to block and data is not immediately + // available in the system cache + CompleteIrp = FALSE; + PostRequest = TRUE; + // Mark Irp Pending ... + try_return(RC = STATUS_PENDING); + } else { + // We have the data + PtrIrp->IoStatus.Status = RC; + PtrIrp->IoStatus.Information = NumberBytesWritten = WriteLength; + } + + } else { + + // If the request extends beyond valid data length, and if the caller + // is not the lazy writer, then utilize CcZeroData() to zero out any + // blocks between current ValidDataLength and the start of the write + // operation. This method of zeroing data is convenient since it avoids + // any unneccessary writes to disk. Of course, if your FSD makes no + // guarantees about reading uninitialized data (native NT FSD + // implementations guarantee that read operations will receive zeroes + // if the sectors were not written to, thereby ensuring that old data + // cannot be re-read unintentionally or maliciously), you can avoid + // performing the zeroing operation altogether. + // You must however be careful about correctly determining the + // top-level component for the IRP so as to be able to extend valid + // data length only when appropriate and also avoid any + // infinite, recursive loops. + // See Chapter 10 for a discussion on this topic. + + // Here is a common method used by Windows NT file system drivers + // that are in the process of sending a request to the disk driver. + // First, mark the IRP as pending, then invoke the lower level driver + // after setting a completion routine. + // Meanwhile, this particular thread can immediately return a + // STATUS_PENDING return code. + // The completion routine is then responsible for completing the IRP + // and unlocking appropriate resources + + IoMarkIrpPending(PtrIrp); + + // Invoke a routine to write information to disk at this time + // You will need to set a completion routine before invoking + // a lower level driver + + CompleteIrp = FALSE; + + try_return(RC = STATUS_PENDING); + } + + try_exit: NOTHING; + + } finally { + // Post IRP if required + if (PostRequest) { + // Implement a routine that will queue up the request to be executed + // later (asynchronously) in the context of a system worker thread. + // See Chapter 10 for details. + + if (PtrResourceAcquired) { + SFsdReleaseResource(PtrResourceAcquired); + } + } else if (CompleteIrp && !(RC == STATUS_PENDING)) { + // For synchronous I/O, the FSD must maintain the current byte offset + // Do not do this however, if I/O is marked as paging-io + if (SynchronousIo && !PagingIo && NT_SUCCESS(RC)) { + PtrFileObject->CurrentByteOffset = RtlLargeIntegerAdd(ByteOffset, + RtlConvertUlongToLargeInteger((unsigned long)NumberBytesWritten)); + } + + // If the write completed successfully and this was not a paging-io + // operation, set a flag in the CCB that indicates that a write was + // performed and that the file time should be updated at cleanup + if (NT_SUCCESS(RC) && !PagingIo) { + SFsdSetFlag(PtrCCB->CCBFlags, SFSD_CCB_MODIFIED); + } + + // If the file size was changed, set a flag in the FCB indicating that + // this occurred. + + // If the request failed, and we had done some nasty stuff like + // extending the file size (including informing the Cache Manager + // about the new file size), and allocating on-disk space etc., undo + // it at this time. + + // Release resources ... + if (PtrResourceAcquired) { + SFsdReleaseResource(PtrResourceAcquired); + } + + // Can complete the IRP here if no exception was encountered + if (!(PtrIrpContext->IrpContextFlags & SFSD_IRP_CONTEXT_EXCEPTION)) { + PtrIrp->IoStatus.Status = RC; + PtrIrp->IoStatus.Information = NumberBytesWritten; + + // Free up the Irp Context + SFsdReleaseIrpContext(PtrIrpContext); + + // complete the IRP + IoCompleteRequest(PtrIrp, IO_DISK_INCREMENT); + } + } // can we complete the IRP ? + } // end of "finally" processing + + return(RC); +} + + + +/************************************************************************* +* +* Function: SFsdDeferredWriteCallBack() +* +* Description: +* Invoked by the cache manager in the context of a worker thread. +* Typically, you can simply post the request at this point (just +* as you would have if the original request could not block) to +* perform the write in the context of a system worker thread. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: None +* +*************************************************************************/ +void SFsdDeferredWriteCallBack ( +void *Context1, // Should be PtrIrpContext +void *Context2) // Should be PtrIrp +{ + // You should typically simply post the request to your internal + // queue of posted requests (just as you would if the original write + // could not be completed because the caller could not block). + // Once you post the request, return from this routine. The write + // will then be retried in the context of a system worker thread +} + + +``` + +`Windows Driver Development/Windows NT File System Internals/filter-high/WS_FTP.LOG`: + +```LOG +97.08.05 15:11 B C:\Users\Erik\NTFdisk\filter-high\readme2.txt --> geode /home/eray readme2.txt +97.08.06 14:20 B C:\Users\erik\NTFdisk\filter-high\readme2.txt <-- geode /work/nt/file.internals/disk readme2.txt + +``` + +`Windows Driver Development/Windows NT File System Internals/filter-high/inc/protos.h`: + +```h +/************************************************************************* +* +* File: protos.h +* +* Module: Sample Filter Driver (Kernel mode execution only) +* +* Description: +* Contains the prototypes for functions in this sample filter driver. +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +#ifndef _SFILTER_PROTOS_H_ +#define _SFILTER_PROTOS_H_ + +/************************************************************************ +* prototypes for the file sfilinit.c +************************************************************************/ +extern NTSTATUS DriverEntry( +PDRIVER_OBJECT DriverObject, // created by the I/O sub-system +PUNICODE_STRING RegistryPath); // path to the registry key + +extern void SFilterInitializeFunctionPointers( +PDRIVER_OBJECT DriverObject); // created by the I/O sub-system + +/************************************************************************ +* prototypes for the file fastio.c +************************************************************************/ +extern BOOLEAN SFilterFastIoCheckIfPossible( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN ULONG Length, +IN BOOLEAN Wait, +IN ULONG LockKey, +IN BOOLEAN CheckForReadOperation, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFilterFastIoRead( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN ULONG Length, +IN BOOLEAN Wait, +IN ULONG LockKey, +OUT PVOID Buffer, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFilterFastIoWrite( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN ULONG Length, +IN BOOLEAN Wait, +IN ULONG LockKey, +OUT PVOID Buffer, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFilterFastIoQueryBasicInfo( +IN PFILE_OBJECT FileObject, +IN BOOLEAN Wait, +OUT PFILE_BASIC_INFORMATION Buffer, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFilterFastIoQueryStdInfo( +IN PFILE_OBJECT FileObject, +IN BOOLEAN Wait, +OUT PFILE_STANDARD_INFORMATION Buffer, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFilterFastIoLock( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN PLARGE_INTEGER Length, +PEPROCESS ProcessId, +ULONG Key, +BOOLEAN FailImmediately, +BOOLEAN ExclusiveLock, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFilterFastIoUnlockSingle( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN PLARGE_INTEGER Length, +PEPROCESS ProcessId, +ULONG Key, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFilterFastIoUnlockAll( +IN PFILE_OBJECT FileObject, +PEPROCESS ProcessId, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFilterFastIoUnlockAllByKey( +IN PFILE_OBJECT FileObject, +PEPROCESS ProcessId, +ULONG Key, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFilterFastIoDeviceControl( +IN PFILE_OBJECT FileObject, +IN BOOLEAN Wait, +IN PVOID InputBuffer, +IN ULONG InputBufferLength, +OUT PVOID OutputBuffer, +IN ULONG OutputBufferLength, +IN ULONG IoControlCode, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject); + +extern void SFilterFastIoAcqCreateSec( +IN PFILE_OBJECT FileObject); + +extern void SFilterFastIoRelCreateSec( +IN PFILE_OBJECT FileObject); + +#if(_WIN32_WINNT >= 0x0400) + +extern void SFilterFastIoDetachDevice( +PDEVICE_OBJECT SourceDeviceObject, // our device object +PDEVICE_OBJECT TargetDeviceObject); + +extern BOOLEAN SFilterFastIoQueryNetInfo( +IN PFILE_OBJECT FileObject, +IN BOOLEAN Wait, +OUT PFILE_NETWORK_OPEN_INFORMATION Buffer, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFilterFastIoMdlRead( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN ULONG Length, +IN ULONG LockKey, +OUT PMDL *MdlChain, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFilterFastIoMdlReadComplete( +IN PFILE_OBJECT FileObject, +OUT PMDL MdlChain, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFilterFastIoPrepareMdlWrite( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN ULONG Length, +IN ULONG LockKey, +OUT PMDL *MdlChain, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFilterFastIoMdlWriteComplete( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +OUT PMDL MdlChain, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFilterFastIoReadCompressed( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN ULONG Length, +IN ULONG LockKey, +OUT PVOID Buffer, +OUT PMDL *MdlChain, +OUT PIO_STATUS_BLOCK IoStatus, +OUT struct _COMPRESSED_DATA_INFO *CompressedDataInfo, +IN ULONG CompressedDataInfoLength, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFilterFastIoWriteCompressed( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN ULONG Length, +IN ULONG LockKey, +OUT PVOID Buffer, +OUT PMDL *MdlChain, +OUT PIO_STATUS_BLOCK IoStatus, +OUT struct _COMPRESSED_DATA_INFO *CompressedDataInfo, +IN ULONG CompressedDataInfoLength, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFilterFastIoMdlReadCompleteCompressed( +IN PFILE_OBJECT FileObject, +OUT PMDL MdlChain, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFilterFastIoMdlWriteCompleteCompressed( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +OUT PMDL MdlChain, +IN PDEVICE_OBJECT DeviceObject); + +extern NTSTATUS SFilterFastIoAcqModWrite( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER EndingOffset, +OUT PERESOURCE *ResourceToRelease, +IN PDEVICE_OBJECT DeviceObject); + +extern NTSTATUS SFilterFastIoRelModWrite( +IN PFILE_OBJECT FileObject, +IN PERESOURCE ResourceToRelease, +IN PDEVICE_OBJECT DeviceObject); + +extern NTSTATUS SFilterFastIoAcqCcFlush( +IN PFILE_OBJECT FileObject, +IN PDEVICE_OBJECT DeviceObject); + +extern NTSTATUS SFilterFastIoRelCcFlush( +IN PFILE_OBJECT FileObject, +IN PDEVICE_OBJECT DeviceObject); + +extern BOOLEAN SFilterFastIoQueryOpen( +IN PIRP Irp, +OUT PFILE_NETWORK_OPEN_INFORMATION NetworkInformation, +IN PDEVICE_OBJECT DeviceObject); + +#endif //_WIN32_WINNT >= 0x0400 + +/************************************************************************ +* prototypes for the file attach.c +************************************************************************/ +extern NTSTATUS SFilterAttachTarget( +PDEVICE_OBJECT PtrTargetDeviceObject, +PDEVICE_OBJECT *PtrNewDeviceObject); + +extern void SFilterDetachTarget( +PDEVICE_OBJECT SourceDeviceObject, +PDEVICE_OBJECT TargetDeviceObject, +PtrSFilterDeviceExtension PtrDeviceExtension); + +/************************************************************************ +* prototypes for the file dispatch.c +************************************************************************/ +extern NTSTATUS SFilterDefaultDispatch ( +PDEVICE_OBJECT DeviceObject, // Our device object +PIRP Irp); // I/O Request Packet + +extern NTSTATUS SFilterDefaultCompletion( +PDEVICE_OBJECT PtrDeviceObject, +PIRP PtrIrp, +void *Context); + +/************************************************************************ +* prototypes for the file misc.c +************************************************************************/ +extern void SFilterInitDevExtension( +PtrSFilterDeviceExtension PtrDeviceExtension, +PDEVICE_OBJECT PtrAssociatedDeviceObject, +uint32 NodeType); + +extern void SFilterDeleteDevExtension( +PtrSFilterDeviceExtension PtrDeviceExtension, +BOOLEAN ResourceAcquired); + +extern NTSTATUS SFilterCreateDirectory( +PWCHAR DirectoryNameStr, +PHANDLE PtrReturnedHandle, +BOOLEAN MakeTemporaryObject); + +extern void SFilterReinitialize( +PDRIVER_OBJECT DriverObject, // representing the filter driver +void *Context, // this filter driver supplies registry path +ULONG Count); // # times this function invoked + +extern void SFilterFSDNotification( +PDEVICE_OBJECT PtrTargetFileSystemDeviceObject, +BOOLEAN DriverActive); + +/************************************************************************ +* prototypes for the file create.c +************************************************************************/ + +extern NTSTATUS SFilterCreate( +PDEVICE_OBJECT DeviceObject, // Our device object +PIRP Irp); // I/O Request Packet + +/************************************************************************ +* prototypes for the file close.c +************************************************************************/ + +extern NTSTATUS SFilterCleanupOrClose( +PDEVICE_OBJECT DeviceObject, // Our device object +PIRP Irp); // I/O Request Packet + +/************************************************************************ +* prototypes for the file fsctrl.c +************************************************************************/ + +extern NTSTATUS SFilterFSControl( +PDEVICE_OBJECT DeviceObject, // Our device object +PIRP Irp); // I/O Request Packet + +extern NTSTATUS SFilterMountVolumeCompletion( +PDEVICE_OBJECT PtrDeviceObject, +PIRP PtrIrp, +void *Context); + +extern void SFilterMountAttach( +void *Context); + +#endif // _SFILTER_PROTOS_H_ + + +``` + +`Windows Driver Development/Windows NT File System Internals/filter-high/inc/sfilter.h`: + +```h +/************************************************************************* +* +* File: sfilter.h +* +* Module: Sample Filter Driver (Kernel mode execution only) +* +* Description: +* The main include file for the sample filter driver. This file contains +* a mish-mash of things. +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +#ifndef _SFILTER_SFILTER_H_ +#define _SFILTER_SFILTER_H_ + +// some constant definitions +#define SFILTER_PANIC_IDENTIFIER (0x86427532) + +#define SFILTER_DRV_DIR L"\\Device\\SFilter" +#define SFILTER_DOS_DRV_DIR L"\\DosDevices\\SFilter" +#define SFILTER_DRV_NAME L"\\Device\\SFilter\\SampleFilterDrv" +#define SFILTER_DOS_DRV_NAME L"\\DosDevices\\SFilter\\SampleFilterDrv" + + +// Common include files - should be in the include dir of the MS supplied FSDDK +#include + +// the following include files should be in the inc sub-dir associated with this driver +#include "struct.h" +#include "protos.h" +// #include "errmsg.h" + +// global variables - minimize these +extern SFilterData SFilterGlobalData; + + +/********************************************************************************** +* +* The following macros make life a little easier +* +**********************************************************************************/ + +// try-finally simulation +#define try_return(S) { S; goto try_exit; } +#define try_return1(S) { S; goto try_exit1; } +#define try_return2(S) { S; goto try_exit2; } + +// Flag (bit-field) manipulation +#define SFilterSetFlag(Flag, Value) ((Flag) |= (Value)) +#define SFilterClearFlag(Flag, Value) ((Flag) &= ~(Value)) + +// Align a value along a 4-byte boundary. +#define SFilterQuadAlign(Value) ((((uint32)(Value)) + 7) & 0xfffffff8) + +// to perform a bug-check (panic), the following macro is used +// it allows us to print out the file identifier, line #, and upto 3 +// additional DWORD arguments. +#define SFilterPanic(arg1, arg2, arg3) \ + (KeBugCheckEx(SFILTER_PANIC_IDENTIFIER, SFILTER_BUG_CHECK_ID | __LINE__, (uint32)(arg1), (uint32)(arg2), (uint32)(arg3))) + +// Release a resource in the context of the thread that originally acquired the +// resource. +#define SFilterReleaseResource(Resource) (ExReleaseResourceForThreadLite((Resource), ExGetCurrentResourceThread())) + +// the following check asserts that the passed-in device extension +// pointer is valid +#define SFilterAssertExtPtrValid(PtrExtension) { \ + ASSERT((PtrExtension)); \ + ASSERT((((PtrExtension)->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_FILTER_DEVICE) || \ + ((PtrExtension)->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE)) &&\ + ((PtrExtension)->NodeIdentifier.NodeSize == sizeof(SFilterDeviceExtension))); \ +} + +// the following macro allows us to increment a large integer value atomically. +// we expect an unsigned long to be supplied as the increment value. +// a spin lock should be passed-in to synchronize operations +#define SFilterIncrementLargeInteger(LargeIntegerOp, ULongIncrement, PtrSpinLock) { \ + KIRQL OldIrql; \ + KeAcquireSpinLock(PtrSpinLock, &OldIrql); \ + RtlLargeIntegerAdd((LargeIntegerOp),(RtlConvertUlongToLargeInteger((ULongIncrement)))); \ + KeReleaseSpinLock(PtrSpinLock, OldIrql); \ +} + +// the following macro allows us to decrement a large integer value atomically. +// we expect an unsigned long to be supplied as the decrement value. +// a spin lock should be passed-in to synchronize operations +#define SFilterDecrementLargeInteger(LargeIntegerOp, ULongIncrement, PtrSpinLock) { \ + KIRQL OldIrql; \ + KeAcquireSpinLock(PtrSpinLock, &OldIrql); \ + RtlLargeIntegerSubtract((LargeIntegerOp),(RtlConvertUlongToLargeInteger((ULongIncrement)))); \ + KeReleaseSpinLock(PtrSpinLock, OldIrql); \ +} + +// the following macro allows us to check if the large integer value is zero, +// atomically. Note that I have added (for convenience) a check to ensure that +// the value is non-negative. +#define SFilterIsLargeIntegerZero(ReturnValue, LargeIntegerOp, PtrSpinLock) { \ + KIRQL OldIrql; \ + KeAcquireSpinLock(PtrSpinLock, &OldIrql); \ + ASSERT(RtlLargeIntegerGreaterOrEqualToZero((LargeIntegerOp))); \ + ReturnValue = RtlLargeIntegerEqualToZero((LargeIntegerOp)); \ + KeReleaseSpinLock(PtrSpinLock, OldIrql); \ +} + +/********************************************************************************** +* +* End of macro definitions +* +**********************************************************************************/ + +// each file has a unique bug-check identifier associated with it. +// Here is a list of constant definitions for these identifiers +#define SFILTER_FILE_INIT (0x00000001) +#define SFILTER_FILE_ATTACH (0x00000002) +#define SFILTER_FILE_FAST_IO (0x00000003) +#define SFILTER_FILE_MISC (0x00000004) +#define SFILTER_FILE_DISPATCH (0x00000005) +#define SFILTER_FILE_CREATE (0x00000006) +#define SFILTER_FILE_CLOSE (0x00000007) +#define SFILTER_FILE_FSCTRL (0x00000008) + +#if DBG +#define SFilterBreakPoint() DbgBreakPoint() +#else +#define SFilterBreakPoint() +#endif + +#endif // _SFILTER_SFILTER_H_ + + +``` + +`Windows Driver Development/Windows NT File System Internals/filter-high/inc/struct.h`: + +```h +/************************************************************************* +* +* File: struct.h +* +* Module: Sample File System Driver (Kernel mode execution only) +* +* Description: +* This file contains structure definitions for the sample filter +* driver. Note that all structures are prefixed with the letters +* "SFilter". The structures are all aligned using normal alignment +* used by the compiler (typically quad-word aligned). +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +#ifndef _SFILTER_STRUCTURES_H_ +#define _SFILTER_STRUCTURES_H_ + +/************************************************************************** + some useful definitions +**************************************************************************/ +#ifdef _CPU_X86_ +typedef char int8; +typedef short int16; +typedef int int32; + +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint32; + +// we will use the LARGE_INTEGER structure as defined by NT + +#else // Please define appropriate types here + +!!!! You must define your types here for compilation to proceed !!!! + +#endif // if _CPU_X86_ + +/************************************************************************** + some empty typedefs defined here so we can reference them easily +**************************************************************************/ +struct _SFilterIdentifier; +struct _SFilterData; + +/************************************************************************** + each structure has a unique "node type" or signature associated with it +**************************************************************************/ +#define SFILTER_NODE_TYPE_GLOBAL_DATA (0xfdecba10) +#define SFILTER_NODE_TYPE_FILTER_DEVICE (0xfdecba11) +#define SFILTER_NODE_TYPE_ATTACHED_DEVICE (0xfdecba12) +#define SFILTER_NODE_TYPE_MOUNT_COMPLETION (0xfdecba13) + +/************************************************************************** + every structure has a node type, and a node size associated with it. + The node type serves as a signature field. The size is used for + consistency checking ... +**************************************************************************/ +typedef struct _SFilterIdentifier { + uint32 NodeType; // a 32 bit identifier for the structure + uint32 NodeSize; // computed as sizeof(structure) +} SFilterIdentifier, *PtrSFilterIdentifier; + +/************************************************************************** + the device extension for each device object created by the filter driver +**************************************************************************/ +typedef struct _SFilterDeviceExtension { + // A signature (including device size). + SFilterIdentifier NodeIdentifier; + // This is used to synchronize access to the device extension structure. + ERESOURCE DeviceExtensionResource; + // For convenience, a back ptr to the device object that contains this + // extension. + PDEVICE_OBJECT PtrAssociatedDeviceObject; + // The sample filter driver keeps a private doubly-linked list of all + // device objects created by the driver. + LIST_ENTRY NextDeviceObject; + // See Flag definitions below. + uint32 DeviceExtensionFlags; + // The device object we are attached to. + PDEVICE_OBJECT TargetDeviceObject; + // Stored for convenience. A pointer to the driver object for the + // target device object (you can always obtain this information from + // the target device object). + PDRIVER_OBJECT TargetDriverObject; + // If we are attached to a mounted volume instance, the actual FSD + // created device object representing the instance of the mounted + // volume. + PDEVICE_OBJECT TargetMountedVolumeDeviceObject; + // A count of outstanding I/O requests for which we have specified a + // completion routine. + LARGE_INTEGER OutstandingIoRequests; + // The OutstandingIoRequests field is protected by an Executive spin lock. + KSPIN_LOCK IoRequestsSpinLock; + // The event object is used to synchronize detach requests with pending + // I/O operations and similar stuff. + KEVENT IoInProgressEvent; + // You can associate other information here. +} SFilterDeviceExtension, *PtrSFilterDeviceExtension; + +#define SFILTER_DEV_EXT_RESOURCE_INITIALIZED (0x00000001) +#define SFILTER_DEV_EXT_INSERTED_GLOBAL_LIST (0x00000002) +#define SFILTER_DEV_EXT_ATTACHED (0x00000004) +#define SFILTER_DEV_EXT_ATTACHED_FSD (0x00000008) + +/************************************************************************** + we will store all of our global variables in one structure. +**************************************************************************/ +typedef struct _SFilterData { + SFilterIdentifier NodeIdentifier; + // The fields in this list are protected by the following resource + ERESOURCE GlobalDataResource; + // Each driver has a driver object created for it by the NT I/O Mgr. + // The filter driver is no exception to this rule. + PDRIVER_OBJECT SFilterDriverObject; + // We will create a device object for our filter driver as well ... + // Although not really required, it helps if a helper application + // writen by us wishes to send us control information via + // IOCTL requests ... + PDEVICE_OBJECT SFilterDeviceObject; + // A driver private list of all device objects created by the filter driver. + LIST_ENTRY NextDeviceObject; + // The NT Cache Manager, the I/O Manager and we will conspire + // to bypass IRP usage using the function pointers contained + // in the following structure + // We will require this if we layer on top of a file system. + FAST_IO_DISPATCH SFilterFastIoDispatch; + // Some state information is maintained in the flags field + uint32 SFilterFlags; + // Handle for the directory object created by the filter driver + HANDLE DirectoryHandle; + // Corresponding handle for the directory visible to the user + HANDLE DosDirectoryHandle; +} SFilterData, *PtrSFilterData; + +// Valid flag values for the global data structure +#define SFILTER_DATA_FLAGS_RESOURCE_INITIALIZED (0x00000001) +#define SFILTER_DATA_FLAGS_SYMLINK_CREATED (0x00000002) + +/************************************************************************** + the following structure is used to pass-in as context to the mount + completion routine. +**************************************************************************/ + +typedef struct _SFilterMountCompletion { + // A signature (including device size). + SFilterIdentifier NodeIdentifier; + // pointer to the target device object + PDEVICE_OBJECT PtrDeviceObject; + // pointer to the original IRP + PIRP PtrOriginalIrp; + // a pointer to the VPB structure in the IRP + PVPB PtrVPB; + // a pointer to our device extension (for the device object attached to the FSD) + PtrSFilterDeviceExtension PtrDeviceExtension; + // an embedded work queue item structure + WORK_QUEUE_ITEM WorkItem; +} SFilterMountCompletion, *PtrSFilterMountCompletion; + + +#endif _SFILTER_STRUCTURES_H_ // has this file been included ? + + +``` + +`Windows Driver Development/Windows NT File System Internals/filter-high/readme2.txt`: + +```txt +Sample Filter Driver +------------------- + +The sample filter driver provided here can be used to design +and implement kernel-mode filter drivers. This driver has been designed and +implemented to allow filtering file system requests. +Note that you should use the driver source provided here in conjunction with +the chapters in the text to better understand the subtleties of implementing +such kernel-mode filter drivers. + +Building and Installing +----------------------- + +1) Set the environment variables by running the SDK SETENV.BAT and the DDK + SETENV.BAT. + +2) Run BUILD.EXE with the -cef option. + +3) The driver is built in the OBJ\I386\CHECKED (or FREE) directory; copy + it to %WINDIR%\SYSTEM32\DRIVERS. + +4) Run REGINI.EXE with SFILTER.INI as an argument. + +5) Reboot the machine (be careful to have a debugger installed and + executing for the target machine). + +Restrictions: +------------ + +This sample driver attaches itself to all FSDs that register with +the I/O Manager. It doesn't attempt to attach itself to the RAW file system, +neither does it concern itself with network redirectors. + +If you wish to use the code with Windows NT 3.51, you may need to +modify the source to open specific FSDs by name. + +How to use: +---------- + +You should modify the source provided here (e.g., add appropriate print +statements) that will aid in understanding how FSD requests are +issued and processed. Use this source to serve as a template in +designing and implementing your own commercial filter driver. + +Files provided: +-------------- + +src\sfilinit.c - Contains DriverEntry function that initializes the driver +src\fastio.c - Contains fast I/O function calls implemented by the filter +src\misc.c - Misc. routines (e.g., to initialize a device extension) +src\fsctrl.c - Code to intercept FSD mount requests (and initiate attach) +src\dispatch.c - The "pass-thru" mode, i.e., we send everything through +src\create.c - Special intercept routine for create/open requests +src\close.c - Special intercept routine for cleanup and close requests +src\attach.c - Contains routines to perform an attach to target and^M + a corresponding detach +inc\protos.h - Prototypes for all functions in files listed above +inc\sfilter.h - The "main" include file; also contains useful macros +inc\struct.h - Structure type definitions. +src\makefile - makefile! +src\sources - Contains list of files to be built; used by build.exe +README.TXT - What you are reading +sfilter.ini - Use this file as an argument to regini.exe to modify + your registry and install the driver. + + + +``` + +`Windows Driver Development/Windows NT File System Internals/filter-high/sfilter.ini`: + +```ini +\Registry\Machine\Software\Windows_NT_File_System_Internals\SFilter + MajorVersionNumber = REG_DWORD 0x0 + MinorVersionNumber = REG_DWORD 0x1 + +\Registry\Machine\System\CurrentControlSet\Services\SFilter + Type = REG_DWORD 0x00000002 + Start = REG_DWORD 0x00000000 + ErrorControl = REG_DWORD 0x00000001 + Group = File System + ImagePath = REG_EXPAND_SZ \SystemRoot\System32\drivers\sfilter.sys + + +``` + +`Windows Driver Development/Windows NT File System Internals/filter-high/src/attach.c`: + +```c +/************************************************************************* +* +* File: attach.c +* +* Module: Sample Filter Driver (Kernel mode execution only) +* +* Description: +* Deals with attaching to and detaching from a target device +* object. Creates a device object as well to be used in the +* attach process. +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +#include "sfilter.h" + +/* define the file specific bug-check id */ +#define SFILTER_BUG_CHECK_ID SFILTER_FILE_ATTACH + + +/************************************************************************* +* +* Function: SFilterAttachTarget() +* +* Description: +* We have the target FSD device object. We will create a new device object +* of our own and attach this newly created device object to the target FSD +* device object (representing the FSD itself or a mounted logical volume). +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFilterAttachTarget( +PDEVICE_OBJECT PtrTargetDeviceObject, +PDEVICE_OBJECT *PtrReturnedDeviceObject) +{ + + NTSTATUS RC = STATUS_SUCCESS; + PDEVICE_OBJECT PtrNewDeviceObject = NULL; // device object we create. + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + BOOLEAN InitializedDeviceObject = FALSE; + BOOLEAN AcquiredDeviceObject = FALSE; + + ASSERT(PtrReturnedDeviceObject); + + try { + + // Create a new device object. + if (!NT_SUCCESS(RC = IoCreateDevice(SFilterGlobalData.SFilterDriverObject, + sizeof(SFilterDeviceExtension), + NULL, // unnamed object + PtrTargetDeviceObject->DeviceType, + PtrTargetDeviceObject->Characteristics, + FALSE, // Not exclusive. + &PtrNewDeviceObject))) { + // failed to create a device object; cannot do much now. + SFilterBreakPoint(); + try_return(RC); + } + + // We do this whenever device objects are create on-the-fly (i.e. not as + // part of driver initialization). + PtrNewDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING; + + // Initialize the extension for the device object. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(PtrNewDeviceObject->DeviceExtension); + SFilterInitDevExtension(PtrDeviceExtension, PtrNewDeviceObject, SFILTER_NODE_TYPE_ATTACHED_DEVICE); + InitializedDeviceObject = TRUE; + + // Acquire the resource exclusively for our newly created device + // object to ensure that dispatch routines requests are not processed + // until we are really ready. + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + AcquiredDeviceObject = TRUE; + + // attach to the target FSD. + RC = IoAttachDeviceByPointer(PtrNewDeviceObject, PtrTargetDeviceObject); + + // The only reaon we would fail (and possibly get STATUS_NO_SUCH_DEVICE) + // is if the target was being initialized or unloaded and neither should + // be happenning at this time. + ASSERT(NT_SUCCESS(RC)); + + // Note that the AlignmentRequirement, the StackSize, and the SectorSize + // values will have been automatically initialized for us in the source + // device object (the I/O Manager does this as part of processing the + // IoAttachDeviceByPointer() request). + + // We should set the Flags values correctly to indicate whether + // direct-IO, buffered-IO, or neither is required. Typically, FSDs + // (especially native FSD implementations) do not want the I/O + // Manager to touch the user buffer at all. + PtrNewDeviceObject->Flags |= (PtrTargetDeviceObject->Flags & (DO_BUFFERED_IO | DO_DIRECT_IO)); + + // Initialize the TargetDeviceObject field in the extension. + PtrDeviceExtension->TargetDeviceObject = PtrTargetDeviceObject; + PtrDeviceExtension->TargetDriverObject = PtrTargetDeviceObject->DriverObject; + SFilterSetFlag(PtrDeviceExtension->DeviceExtensionFlags, SFILTER_DEV_EXT_ATTACHED); + SFilterSetFlag(PtrDeviceExtension->DeviceExtensionFlags, SFILTER_DEV_EXT_ATTACHED_FSD); + + // We are there now. All I/O requests will start being redirected to + // us until we detach ourselves. + + try_exit: NOTHING; + + } finally { + // Cleanup stuff goes here. + if (AcquiredDeviceObject) { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + AcquiredDeviceObject = FALSE; + } + + if (!NT_SUCCESS(RC) && PtrNewDeviceObject) { + if (InitializedDeviceObject) { + // The detach routine will take care of everything. + SFilterDetachTarget(PtrNewDeviceObject, PtrTargetDeviceObject, PtrDeviceExtension); + } + } else { + *PtrReturnedDeviceObject = PtrNewDeviceObject; + } + } + + return(RC); +} + + + +/************************************************************************* +* +* Function: SFilterDetachTarget() +* +* Description: +* This routine will detach from the target device object. It will delete +* the locally created device object as well. Note that we must ensure that +* no I/O requests are outstanding when this occurs (or we should drop them). +* Finally, this routine could be invoked as a result of a fast detach call. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: None +* +*************************************************************************/ +void SFilterDetachTarget( +PDEVICE_OBJECT SourceDeviceObject, +PDEVICE_OBJECT TargetDeviceObject, +PtrSFilterDeviceExtension PtrDeviceExtension) +{ + BOOLEAN AcquiredDevice = FALSE; + BOOLEAN NoRequestsOutstanding = FALSE; + + ASSERT(SourceDeviceObject); + ASSERT(PtrDeviceExtension); + + try { + try { + ASSERT(PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_RESOURCE_INITIALIZED); + + // We will wait until all IRP-based I/O requests have been completed. + + while (TRUE) { + // Acquire the device object resource exclusively. + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + AcquiredDevice = TRUE; + + // Check if there are requests outstanding + SFilterIsLargeIntegerZero(NoRequestsOutstanding, PtrDeviceExtension->OutstandingIoRequests, + &(PtrDeviceExtension->IoRequestsSpinLock)); + + if (!NoRequestsOutstanding) { + // Drop the resource and go to sleep. + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + AcquiredDevice = FALSE; + + // Worst case, we will allow a few new I/O requests to slip in ... + KeWaitForSingleObject((void *)(&(PtrDeviceExtension->IoInProgressEvent)), + Executive, KernelMode, FALSE, NULL); + } else { + break; + } + } + + ASSERT(AcquiredDevice); + + // Detach if attached. + if (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED) { + IoDetachDevice(PtrDeviceExtension->TargetDeviceObject); + SFilterClearFlag(PtrDeviceExtension->DeviceExtensionFlags, SFILTER_DEV_EXT_ATTACHED); + // Blindly clear the attached-to-FSD flag + SFilterClearFlag(PtrDeviceExtension->DeviceExtensionFlags, SFILTER_DEV_EXT_ATTACHED_FSD); + } + + // Delete our device object. But first, take care of the device extension. + SFilterDeleteDevExtension(PtrDeviceExtension, TRUE); + AcquiredDevice = FALSE; + // Note that on 4.0 and later systems, this will result in a recursive fast detach. + IoDeleteDevice(SourceDeviceObject); + + } except (EXCEPTION_EXECUTE_HANDLER) { + // Eat it up. + ; + } + + try_exit: NOTHING; + + } finally { + if (AcquiredDevice) { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + AcquiredDevice = FALSE; + } + } + + return; +} + + +``` + +`Windows Driver Development/Windows NT File System Internals/filter-high/src/close.c`: + +```c +/************************************************************************* +* +* File: create.c +* +* Module: Sample Filter Driver (Kernel mode execution only) +* +* Description: +* Cleanup/close entry points. +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +#include "sfilter.h" + +/* define the file specific bug-check id */ +#define SFILTER_BUG_CHECK_ID SFILTER_FILE_CLOSE + + +/************************************************************************* +* +* Function: SFilterCleanupOrClose() +* +* Description: +* Intercept the cleanup/close request targeted to the FSD mounted logical +* volume. This could also be a cleanup/close targeted to this filter driver in +* which case we simply return success. +* +* Expected Interrupt Level (for execution) : +* +* Typically (though not guaranteed) @ IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFilterCleanupOrClose( +PDEVICE_OBJECT DeviceObject, // Our device object +PIRP Irp) // I/O Request Packet +{ + NTSTATUS RC = STATUS_SUCCESS; + PIO_STACK_LOCATION PtrNextIoStackLocation = NULL; + PIO_STACK_LOCATION PtrCurrentStackLocation = NULL; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + BOOLEAN AcquiredExtension = FALSE; + BOOLEAN CompleteIrp = FALSE; + PDEVICE_OBJECT PtrTargetDeviceObject = NULL; + ULONG ReturnedInformation = 0; + + try { + + // Get the current I/O stack location. + PtrCurrentStackLocation = IoGetCurrentIrpStackLocation(Irp); + ASSERT(PtrCurrentStackLocation); + + // Get a pointer to the device extension that must exist for + // all of the device objects created by the filter driver. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(DeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // We really should not be here if our resource is not initialized? + ASSERT(PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_RESOURCE_INITIALIZED); + + // acquire the resource associated with particular device extension for + // our target device object. + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + AcquiredExtension = TRUE; + + // If the device extension is for a lower device forward the request. + if ((PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) + && (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED)) { + + if (AcquiredExtension) { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + AcquiredExtension = FALSE; + } + + RC = SFilterDefaultDispatch(DeviceObject, Irp); + try_return(RC); + } else if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_FILTER_DEVICE) { + // This request was targeted to us. Simply return success. + CompleteIrp = TRUE; + try_return(RC); + } + + // The filter driver does not particularly care to respond to these requests. + CompleteIrp = TRUE; + // does not matter what the Information field contains. + try_return(RC = STATUS_INVALID_DEVICE_REQUEST); + + try_exit: NOTHING; + + } finally { + + // Release any resources acquired. + if (AcquiredExtension) { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + AcquiredExtension = FALSE; + } + + // Complete the IRP only if we must. + if (CompleteIrp) { + Irp->IoStatus.Status = RC; + Irp->IoStatus.Information = ReturnedInformation; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + } + } + + return(RC); +} + + +``` + +`Windows Driver Development/Windows NT File System Internals/filter-high/src/create.c`: + +```c +/************************************************************************* +* +* File: create.c +* +* Module: Sample Filter Driver (Kernel mode execution only) +* +* Description: +* Create/open entry point. +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +#include "sfilter.h" + +/* define the file specific bug-check id */ +#define SFILTER_BUG_CHECK_ID SFILTER_FILE_CREATE + + +/************************************************************************* +* +* Function: SFilterCreate() +* +* Description: +* Intercept the create/open request targeted to the FSD mounted logical +* volume. This could also be a create targeted to this filter driver in +* which case we simply return success. +* +* Expected Interrupt Level (for execution) : +* +* Typically (though not guaranteed) @ IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFilterCreate( +PDEVICE_OBJECT DeviceObject, // Our device object +PIRP Irp) // I/O Request Packet +{ + NTSTATUS RC = STATUS_SUCCESS; + PIO_STACK_LOCATION PtrNextIoStackLocation = NULL; + PIO_STACK_LOCATION PtrCurrentStackLocation = NULL; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + BOOLEAN AcquiredExtension = FALSE; + BOOLEAN CompleteIrp = FALSE; + PDEVICE_OBJECT PtrTargetDeviceObject = NULL; + ULONG ReturnedInformation = FILE_OPENED; + + try { + + // Get the current I/O stack location. + PtrCurrentStackLocation = IoGetCurrentIrpStackLocation(Irp); + ASSERT(PtrCurrentStackLocation); + + // Get a pointer to the device extension that must exist for + // all of the device objects created by the filter driver. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(DeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // We really should not be here if our resource is not initialized? + ASSERT(PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_RESOURCE_INITIALIZED); + + // acquire the resource associated with particular device extension for + // our target device object. + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + AcquiredExtension = TRUE; + + // If the device extension is for a lower device forward the request. + if ((PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) + && (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED)) { + + // Currently, this filter driver simply forwards the request. + // However, you could do all sorts of stuff when you intercept a + // create/open request. For example, if you wish, you could "massage" + // the name and return reparse to the caller which would allow you + // to essentially redirect create/open requests. + + if (AcquiredExtension) { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + AcquiredExtension = FALSE; + } + + RC = SFilterDefaultDispatch(DeviceObject, Irp); + try_return(RC); + } else if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_FILTER_DEVICE) { + // This request was targeted to us. Simply return success. + // You may wish to ensure that an open/open-if type of request + // was sent. You may also wish to ensure that the caller has + // appropriate privileges. Currently, the filter driver does + // not perform such checks. + CompleteIrp = TRUE; + try_return(RC); + } + + // The filter driver does not particularly care to respond to these requests. + CompleteIrp = TRUE; + // does not matter what the Information field contains. + try_return(RC = STATUS_INVALID_DEVICE_REQUEST); + + try_exit: NOTHING; + + } finally { + + // Release any resources acquired. + if (AcquiredExtension) { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + AcquiredExtension = FALSE; + } + + // Complete the IRP only if we must. + if (CompleteIrp) { + Irp->IoStatus.Status = RC; + Irp->IoStatus.Information = ReturnedInformation; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + } + } + + return(RC); +} + + +``` + +`Windows Driver Development/Windows NT File System Internals/filter-high/src/dispatch.c`: + +```c +/************************************************************************* +* +* File: dispatch.c +* +* Module: Sample Filter Driver (Kernel mode execution only) +* +* Description: +* Dispatch entry points. +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +#include "sfilter.h" + +/* define the file specific bug-check id */ +#define SFILTER_BUG_CHECK_ID SFILTER_FILE_DISPATCH + + +/************************************************************************* +* +* Function: SFilterDefaultDispatch() +* +* Description: +* Simply forward the request to the device we are attached to. +* Return INVALID_REQUEST if we are not attached to anything. +* Before forwarding the request, set up a completion routine +* that can be used to do interesting stuff (if so desired). +* +* Expected Interrupt Level (for execution) : +* +* Typically (though not guaranteed) @ IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFilterDefaultDispatch( +PDEVICE_OBJECT DeviceObject, // Our device object +PIRP Irp) // I/O Request Packet +{ + NTSTATUS RC = STATUS_SUCCESS; + PIO_STACK_LOCATION PtrNextIoStackLocation = NULL; + PIO_STACK_LOCATION PtrCurrentStackLocation = NULL; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + BOOLEAN AcquiredExtension = FALSE; + BOOLEAN CompleteIrp = FALSE; + PDEVICE_OBJECT PtrTargetDeviceObject = NULL; + ULONG ReturnedInformation = 0; + + try { + + // Get the current I/O stack location. + PtrCurrentStackLocation = IoGetCurrentIrpStackLocation(Irp); + ASSERT(PtrCurrentStackLocation); + + // Get a pointer to the device extension that must exist for + // all of the device objects created by the filter driver. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(DeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // We really should not be here if our resource is not initialized? + ASSERT(PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_RESOURCE_INITIALIZED); + + // acquire the resource associated with particular device extension for + // our target device object. + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + AcquiredExtension = TRUE; + + // If the device extension is for a lower device forward the request. + if ((PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) + && (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED)) { + + // Be careful about not screwing up badly. This is actually not recommended by the I/O Manager. + if (Irp->CurrentLocation == 1) { + // Bad!! Fudge the error code. Break if we can ... + SFilterBreakPoint(); + CompleteIrp = TRUE; + try_return(RC = STATUS_INVALID_DEVICE_REQUEST); + } + + PtrNextIoStackLocation = IoGetNextIrpStackLocation(Irp); + + // So far, so good! Copy over the contents of the current stack + // location into the next IRP stack location. Be careful about + // how we do the copy. The following statement is convenient + // but will end up screwing up any driver above us who has + // set a completion routine! + *PtrNextIoStackLocation = *PtrCurrentStackLocation; + + // We will specify a default completion routine. This will + // prevent any completion routine being invoked twice + // (set by a driver above us in the calling hierarchy) and also + // allow us the opportunity to do whatever we like once the + // function processing has been completed. + // We will specify that our completion routine be invoked regardless + // of how the IRP is completed/cancelled. + IoSetCompletionRoutine(Irp, SFilterDefaultCompletion, PtrDeviceExtension, TRUE, TRUE, TRUE); + + // Forward the request. Note that if the target does not + // wish to service the function, the request will get redirected + // to IopInvalidDeviceRequest() (a routine that completes the + // IRP with STATUS_INVALID_DEVICE_REQUEST). + // However, we must release our resources before forwarding the + // request. That will avoid the sort of problems discussed in + // Chapter 12 of the text. + + PtrTargetDeviceObject = PtrDeviceExtension->TargetDeviceObject; + + // Increment the count of outstanding I/O requests. The count will + // be decremented in the completion routine. + // Acquire a special end-resource spin-lock to synchronize access. + SFilterIncrementLargeInteger(PtrDeviceExtension->OutstandingIoRequests, + (unsigned long)1, + &(PtrDeviceExtension->IoRequestsSpinLock)); + + // Clear the fast-IO notification event protected by the resource + // we have acquired. + KeClearEvent(&(PtrDeviceExtension->IoInProgressEvent)); + + KdPrint(("SFilterDefaultDispatch(): Target Device Object = 0x%x, Major function = 0x%x, Minor Function = 0x%x\n", + PtrTargetDeviceObject, PtrCurrentStackLocation->MajorFunction, PtrCurrentStackLocation->MinorFunction)); + + if (AcquiredExtension) { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + AcquiredExtension = FALSE; + } + + RC = IoCallDriver(PtrTargetDeviceObject, Irp); + + // Note that at this time, the filter driver completion routine + // does not return STATUS_MORE_PROCESSING_REQUIRED. However, if you + // do modify this code and use it in your own filter driver and if your + // completion routine *could* return the STATUS_MORE_PROCESSING_REQUIRED + // return code, you must not blindly return the return-code obtained from + // the call to IoCallDriver() above. See Chapter 12 for a discussion of + // this issue. + try_return(RC); + } + + // The filter driver does not particularly care to respond to these requests. + CompleteIrp = TRUE; + try_return(RC = STATUS_INVALID_DEVICE_REQUEST); + + try_exit: NOTHING; + + } finally { + + // Release any resources acquired. + if (AcquiredExtension) { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + AcquiredExtension = FALSE; + } + + // Complete the IRP only if we must. + if (CompleteIrp) { + Irp->IoStatus.Status = RC; + Irp->IoStatus.Information = ReturnedInformation; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + } + } + + return(RC); +} + + + +/************************************************************************* +* +* Function: SFilterDefaultCompletion() +* +* Description: +* Decrement the outstanding I/O count. Then let it go. Note that you +* must always be aware that the completion routine could be invoked +* at high IRQL and in the context of some arbitrary thread. +* +* Expected Interrupt Level (for execution) : +* +* Typically at high IRQL. Occasionally at IRQL PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/STATUS_MORE_PROCESSING_REQUIRED +* +*************************************************************************/ +NTSTATUS SFilterDefaultCompletion( +PDEVICE_OBJECT PtrDeviceObject, +PIRP PtrIrp, +void *Context) +{ + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + BOOLEAN CanDetachProceed = FALSE; + PDEVICE_OBJECT PtrAssociatedDeviceObject = NULL; + + // acquire the device extension resource exclusively. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(Context); + ASSERT(PtrDeviceExtension); + + if (PtrIrp->PendingReturned) { + IoMarkIrpPending(PtrIrp); + } + + PtrAssociatedDeviceObject = PtrDeviceExtension->PtrAssociatedDeviceObject; + + // Ensure that this is a valid device object pointer, else return + // immediately. + if (PtrAssociatedDeviceObject != PtrDeviceObject) { + // Bug exposed; see Chapter 12 for details. + return(STATUS_SUCCESS); + } + + // Note that you could do all sorts of processing at this point + // depending upon the results of the operation. Be careful though + // about what you chose to do, about the fact that this completion + // routine is being invoked in an arbitrary thread context and probably + // at high IRQL. + + SFilterDecrementLargeInteger(PtrDeviceExtension->OutstandingIoRequests, + (unsigned long)1, + &(PtrDeviceExtension->IoRequestsSpinLock)); + + // If the outstanding count is 0, signal the appropriate event which will + // allow any pending detach to proceed. + SFilterIsLargeIntegerZero(CanDetachProceed, PtrDeviceExtension->OutstandingIoRequests, + &(PtrDeviceExtension->IoRequestsSpinLock)); + + if (CanDetachProceed) { + // signal the event object. Note that this is simply an + // advisory check we do here (to wake up a sleeping thread). + // It is the responsibility of the thread performing the detach to + // ensure that no operations are truly in progress. + KeSetEvent(&(PtrDeviceExtension->IoInProgressEvent), IO_NO_INCREMENT, FALSE); + } + + // Although the success return value is hard-coded here, you can + // return an appropriate value (either success or more-processing-reqd) + // based upon what it is that you wish to do in your completion routine. + return(STATUS_SUCCESS); +} + + +``` + +`Windows Driver Development/Windows NT File System Internals/filter-high/src/fastio.c`: + +```c +/************************************************************************* +* +* File: fastio.c +* +* Module: Sample Filter Driver (Kernel mode execution only) +* +* Description: +* Contains code to handle the various "fast-io" calls. +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +#include "sfilter.h" + +/* define the file specific bug-check id */ +#define SFILTER_BUG_CHECK_ID SFILTER_FILE_FAST_IO + + + +/************************************************************************* +* +* Function: SFilterFastIoCheckIfPossible() +* +* Description: +* To fast-io or not to fast-io, that is the question ... +* This routine helps the I/O Manager determine whether the FSD wishes +* to permit fast-io on a specific file stream. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFilterFastIoCheckIfPossible( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN ULONG Length, +IN BOOLEAN Wait, +IN ULONG LockKey, +IN BOOLEAN CheckForReadOperation, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject) +{ + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + FAST_IO_POSSIBLE RC = FastIoIsNotPossible; + + try { + // Get a pointer to the device extension. Must be ours. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(DeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // If the device extension is for a lower device forward the request. + if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) { + PFAST_IO_DISPATCH PtrFastIoDispatch = NULL; + + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + try { + // Just to synchronize with the attach and detach operations. + // Note that dynamic detaching is risky anyways. + if (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED) { + PtrFastIoDispatch = PtrDeviceExtension->TargetDriverObject->FastIoDispatch; + } + } finally { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + } + + // Note that although I am checking for a valid "FastIoCheckIfPossible" + // function pointer below, the only reason the caller invoked this + // function is because the CommonFCBHeader in the file object asked + // it to check whether fast-IO was possible or not (i.e. the + // IsFastIoPossible field was set to FastIoIsQuestionable). In that + // case, the target of our attach operation (if it is the original + // file system itself) better have a valid FastIoCheckIfPossible + // field. However. we may have layered ourselves over a filter driver + // in which case it seems prudent to make the check below. + if (PtrFastIoDispatch && PtrFastIoDispatch->FastIoCheckIfPossible) { + RC = PtrFastIoDispatch->FastIoCheckIfPossible(FileObject, FileOffset, Length, Wait, LockKey, + CheckForReadOperation, IoStatus, + PtrDeviceExtension->TargetDeviceObject); + } + try_return(RC); + } + + try_exit: NOTHING; + + } finally { + ; + } + + return(RC); +} + + +/************************************************************************* +* +* Function: SFilterFastIoRead() +* +* Description: +* Bypass the traditional IRP method to perform a read operation. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFilterFastIoRead( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN ULONG Length, +IN BOOLEAN Wait, +IN ULONG LockKey, +OUT PVOID Buffer, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN RC = FALSE; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + + try { + + // Get a pointer to the device extension. Must be ours. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(DeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // If the device extension is for a lower device forward the request. + if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) { + PFAST_IO_DISPATCH PtrFastIoDispatch = NULL; + + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + try { + // Just to synchronize with the attach and detach operations. + if (PtrDeviceExtension->DeviceExtensionFlags & + SFILTER_DEV_EXT_ATTACHED) { + PtrFastIoDispatch = PtrDeviceExtension->TargetDriverObject->FastIoDispatch; + } + } finally { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + } + + if (PtrFastIoDispatch && PtrFastIoDispatch->FastIoRead) { + RC = PtrFastIoDispatch->FastIoRead(FileObject, FileOffset, Length, Wait, LockKey, + Buffer, IoStatus, + PtrDeviceExtension->TargetDeviceObject); + } + try_return(RC); + } + + // Otherwise, we return FALSE to indicate that the operation cannot + // be performed via the fast path. + + try_exit: NOTHING; + + } finally { + ; + } + + return(RC); +} + + +/************************************************************************* +* +* Function: SFilterFastIoWrite() +* +* Description: +* Bypass the traditional IRP method to perform a write operation. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFilterFastIoWrite( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN ULONG Length, +IN BOOLEAN Wait, +IN ULONG LockKey, +OUT PVOID Buffer, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject) +{ + + BOOLEAN RC = FALSE; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + + try { + + // Get a pointer to the device extension. Must be ours. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(DeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // If the device extension is for a lower device forward the request. + if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) { + PFAST_IO_DISPATCH PtrFastIoDispatch = NULL; + + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + try { + // Just to synchronize with the attach and detach operations. + if (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED) { + PtrFastIoDispatch = + PtrDeviceExtension->TargetDriverObject->FastIoDispatch; + } + } finally { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + } + + if (PtrFastIoDispatch && PtrFastIoDispatch->FastIoWrite) { + RC = PtrFastIoDispatch->FastIoWrite(FileObject, FileOffset, Length, Wait, LockKey, + Buffer, IoStatus, + PtrDeviceExtension->TargetDeviceObject); + } + try_return(RC); + } + + // Otherwise, we return FALSE to indicate that the operation cannot + // be performed via the fast path. + + try_exit: NOTHING; + + } finally { + ; + } + + return(RC); +} + + +/************************************************************************* +* +* Function: SFilterFastIoQueryBasicInfo() +* +* Description: +* Bypass the traditional IRP method to perform a query basic +* information operation. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFilterFastIoQueryBasicInfo( +IN PFILE_OBJECT FileObject, +IN BOOLEAN Wait, +OUT PFILE_BASIC_INFORMATION Buffer, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject) +{ + + BOOLEAN RC = FALSE; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + + try { + + // Get a pointer to the device extension. Must be ours. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(DeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // If the device extension is for a lower device forward the request. + if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) { + PFAST_IO_DISPATCH PtrFastIoDispatch = NULL; + + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + try { + // Just to synchronize with the attach and detach operations. + if (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED) { + PtrFastIoDispatch = PtrDeviceExtension->TargetDriverObject->FastIoDispatch; + } + } finally { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + } + + if (PtrFastIoDispatch && PtrFastIoDispatch->FastIoQueryBasicInfo) { + RC = PtrFastIoDispatch->FastIoQueryBasicInfo(FileObject, Wait, Buffer, IoStatus, + PtrDeviceExtension->TargetDeviceObject); + } + try_return(RC); + } + + // Otherwise, we return FALSE to indicate that the operation cannot + // be performed via the fast path. + + try_exit: NOTHING; + + } finally { + ; + } + + return(RC); +} + + +/************************************************************************* +* +* Function: SFilterFastIoQueryStdInfo() +* +* Description: +* Bypass the traditional IRP method to perform a query standard +* information operation. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFilterFastIoQueryStdInfo( +IN PFILE_OBJECT FileObject, +IN BOOLEAN Wait, +OUT PFILE_STANDARD_INFORMATION Buffer, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject) +{ + + BOOLEAN RC = FALSE; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + + try { + + // Get a pointer to the device extension. Must be ours. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(DeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // If the device extension is for a lower device forward the request. + if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) { + PFAST_IO_DISPATCH PtrFastIoDispatch = NULL; + + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + try { + // Just to synchronize with the attach and detach operations. + if (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED) { + PtrFastIoDispatch = PtrDeviceExtension->TargetDriverObject->FastIoDispatch; + } + } finally { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + } + + if (PtrFastIoDispatch && PtrFastIoDispatch->FastIoQueryStandardInfo) { + RC = PtrFastIoDispatch->FastIoQueryStandardInfo(FileObject, Wait, Buffer, IoStatus, + PtrDeviceExtension->TargetDeviceObject); + } + try_return(RC); + } + + // Otherwise, we return FALSE to indicate that the operation cannot + // be performed via the fast path. + + try_exit: NOTHING; + + } finally { + ; + } + + return(RC); +} + + +/************************************************************************* +* +* Function: SFilterFastIoLock() +* +* Description: +* Bypass the traditional IRP method to perform a byte range lock +* operation. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFilterFastIoLock( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN PLARGE_INTEGER Length, +PEPROCESS ProcessId, +ULONG Key, +BOOLEAN FailImmediately, +BOOLEAN ExclusiveLock, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN RC = FALSE; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + + try { + + // Get a pointer to the device extension. Must be ours. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(DeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // If the device extension is for a lower device forward the request. + if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) { + PFAST_IO_DISPATCH PtrFastIoDispatch = NULL; + + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + try { + // Just to synchronize with the attach and detach operations. + if (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED) { + PtrFastIoDispatch = PtrDeviceExtension->TargetDriverObject->FastIoDispatch; + } + } finally { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + } + + if (PtrFastIoDispatch && PtrFastIoDispatch->FastIoLock) { + RC = PtrFastIoDispatch->FastIoLock(FileObject, FileOffset, Length, ProcessId, Key, FailImmediately, + ExclusiveLock, + IoStatus, PtrDeviceExtension->TargetDeviceObject); + } + try_return(RC); + } + + // Otherwise, we return FALSE to indicate that the operation cannot + // be performed via the fast path. + + try_exit: NOTHING; + + } finally { + ; + } + + return(RC); +} + + +/************************************************************************* +* +* Function: SFilterFastIoUnlockSingle() +* +* Description: +* Bypass the traditional IRP method to perform a byte range unlock +* operation. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFilterFastIoUnlockSingle( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN PLARGE_INTEGER Length, +PEPROCESS ProcessId, +ULONG Key, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN RC = FALSE; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + + try { + + // Get a pointer to the device extension. Must be ours. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(DeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // If the device extension is for a lower device forward the request. + if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) { + PFAST_IO_DISPATCH PtrFastIoDispatch = NULL; + + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + try { + // Just to synchronize with the attach and detach operations. + if (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED) { + PtrFastIoDispatch = PtrDeviceExtension->TargetDriverObject->FastIoDispatch; + } + } finally { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + } + + if (PtrFastIoDispatch && PtrFastIoDispatch->FastIoUnlockSingle) { + RC = PtrFastIoDispatch->FastIoUnlockSingle(FileObject, FileOffset, Length, ProcessId, Key, IoStatus, + PtrDeviceExtension->TargetDeviceObject); + } + try_return(RC); + } + + // Otherwise, we return FALSE to indicate that the operation cannot + // be performed via the fast path. + + try_exit: NOTHING; + + } finally { + ; + } + + return(RC); +} + + +/************************************************************************* +* +* Function: SFilterFastIoUnlockAll() +* +* Description: +* Bypass the traditional IRP method to perform multiple byte range unlock +* operations. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFilterFastIoUnlockAll( +IN PFILE_OBJECT FileObject, +PEPROCESS ProcessId, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN RC = FALSE; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + + try { + + // Get a pointer to the device extension. Must be ours. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(DeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // If the device extension is for a lower device forward the request. + if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) { + PFAST_IO_DISPATCH PtrFastIoDispatch = NULL; + + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + try { + // Just to synchronize with the attach and detach operations. + if (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED) { + PtrFastIoDispatch = PtrDeviceExtension->TargetDriverObject->FastIoDispatch; + } + } finally { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + } + + if (PtrFastIoDispatch && PtrFastIoDispatch->FastIoUnlockAll) { + RC = PtrFastIoDispatch->FastIoUnlockAll(FileObject, ProcessId, IoStatus, PtrDeviceExtension->TargetDeviceObject); + } + try_return(RC); + } + + // Otherwise, we return FALSE to indicate that the operation cannot + // be performed via the fast path. + + try_exit: NOTHING; + + } finally { + ; + } + + return(RC); +} + + +/************************************************************************* +* +* Function: SFilterFastIoUnlockAllByKey() +* +* Description: +* Bypass the traditional IRP method to perform multiple byte range unlock +* operations. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFilterFastIoUnlockAllByKey( +IN PFILE_OBJECT FileObject, +PEPROCESS ProcessId, +ULONG Key, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN RC = FALSE; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + + try { + + // Get a pointer to the device extension. Must be ours. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(DeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // If the device extension is for a lower device forward the request. + if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) { + PFAST_IO_DISPATCH PtrFastIoDispatch = NULL; + + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + try { + // Just to synchronize with the attach and detach operations. + if (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED) { + PtrFastIoDispatch = PtrDeviceExtension->TargetDriverObject->FastIoDispatch; + } + } finally { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + } + + if (PtrFastIoDispatch && PtrFastIoDispatch->FastIoUnlockAllByKey) { + RC = PtrFastIoDispatch->FastIoUnlockAllByKey(FileObject, ProcessId, + Key, IoStatus, PtrDeviceExtension->TargetDeviceObject); + } + try_return(RC); + } + + // Otherwise, we return FALSE to indicate that the operation cannot + // be performed via the fast path. + + try_exit: NOTHING; + + } finally { + ; + } + + return(RC); +} + + + +/************************************************************************* +* +* Function: SFilterFastIoDeviceControl() +* +* Description: +* If this is our device object, we should process the device control +* request, otherwise, we should forward it on to the target device +* object. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFilterFastIoDeviceControl( +IN PFILE_OBJECT FileObject, +IN BOOLEAN Wait, +IN PVOID InputBuffer, +IN ULONG InputBufferLength, +OUT PVOID OutputBuffer, +IN ULONG OutputBufferLength, +IN ULONG IoControlCode, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN RC = FALSE; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + + try { + + // Get a pointer to the device extension. Must be ours. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(DeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // If the device extension is for a lower device forward the request. + if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) { + PFAST_IO_DISPATCH PtrFastIoDispatch = NULL; + + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + try { + // Just to synchronize with the attach and detach operations. + if (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED) { + PtrFastIoDispatch = PtrDeviceExtension->TargetDriverObject->FastIoDispatch; + } + } finally { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + } + + if (PtrFastIoDispatch && PtrFastIoDispatch->FastIoDeviceControl) { + RC = PtrFastIoDispatch->FastIoDeviceControl(FileObject, + Wait, InputBuffer, InputBufferLength, OutputBuffer, + OutputBufferLength, IoControlCode, IoStatus, + PtrDeviceExtension->TargetDeviceObject); + } + try_return(RC); + } else { + // If you wish to receive device control calls to your FSD, implement + // support for such calls here. + NOTHING; + } + + try_exit: NOTHING; + + } finally { + ; + } + + return(RC); +} + + + + +/************************************************************************* +* +* Function: SFilterFastIoAcqCreateSec() +* +* Description: +* Not really a fast-io operation. Used by the VMM to acquire FSD resources +* before processing a file map (create section object) request. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL ? +* +* Return Value: None (we must be prepared to handle VMM initiated calls) +* +*************************************************************************/ +void SFilterFastIoAcqCreateSec( +IN PFILE_OBJECT FileObject) +{ + PDEVICE_OBJECT PtrLocalDeviceObject = NULL; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + + // Forward rhe request onwards. Of course, if we had to pre-acquire + // any local resources, it should be done now. However, it is prudent + // in the filter not to have any complicated resource acquisition + // methodologies. + // Unfortunately, we will have to look at the file object to determine + // which target device object the request needs to be forwarded to. + // Do exactly what is done by (say) the FSRTL package. + PtrLocalDeviceObject = IoGetRelatedDeviceObject(FileObject); + + PtrDeviceExtension = (PtrSFilterDeviceExtension)(PtrLocalDeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // If the device extension is for a lower device forward the request. + if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) { + PFAST_IO_DISPATCH PtrFastIoDispatch = NULL; + + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + try { + // Just to synchronize with the attach and detach operations. + if (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED) { + PtrFastIoDispatch = PtrDeviceExtension->TargetDriverObject->FastIoDispatch; + } + } finally { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + } + + if (PtrFastIoDispatch && PtrFastIoDispatch->AcquireFileForNtCreateSection) { + // Forward the request so that the next driver in the hierarchy acquires + // it's resources. + PtrFastIoDispatch->AcquireFileForNtCreateSection(FileObject); + } + } + + // Nothing we currently wish to do if the request is directed to a non-attached + // device object. + + return; +} + + +/************************************************************************* +* +* Function: SFilterFastIoRelCreateSec() +* +* Description: +* Not really a fast-io operation. Used by the VMM to release FSD resources +* after processing a file map (create section object) request. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL ? +* +* Return Value: None +* +*************************************************************************/ +void SFilterFastIoRelCreateSec( +IN PFILE_OBJECT FileObject) +{ + PDEVICE_OBJECT PtrLocalDeviceObject = NULL; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + + PtrLocalDeviceObject = IoGetRelatedDeviceObject(FileObject); + + PtrDeviceExtension = (PtrSFilterDeviceExtension)(PtrLocalDeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // If the device extension is for a lower device forward the request. + if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) { + PFAST_IO_DISPATCH PtrFastIoDispatch = NULL; + + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + try { + // Just to synchronize with the attach and detach operations. + if (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED) { + PtrFastIoDispatch = PtrDeviceExtension->TargetDriverObject->FastIoDispatch; + } + } finally { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + } + + if (PtrFastIoDispatch && PtrFastIoDispatch->ReleaseFileForNtCreateSection) { + // Forward the request. + PtrFastIoDispatch->ReleaseFileForNtCreateSection(FileObject); + } + } + + return; +} + +// The remaining are only valid under NT Version 4.0 and later. +#if(_WIN32_WINNT >= 0x0400) + + +/************************************************************************* +* +* Function: SFilterFastIoDetachDevice() +* +* Description: +* The device object we have attached to is being deleted. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: None +* +*************************************************************************/ +void SFilterFastIoDetachDevice( +PDEVICE_OBJECT SourceDeviceObject, // our device object +PDEVICE_OBJECT TargetDeviceObject) +{ + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + + PtrDeviceExtension = (PtrSFilterDeviceExtension)(SourceDeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + KdPrint(("SFilterFastIoDetachDevice(): Detach device requested. Source object = 0x%x, Target Object = 0x%x\n", + SourceDeviceObject, TargetDeviceObject)); + + // The following routine will detach our device object and also + // request a delete operation on our device object which in turn + // will cause any device object attached to ours to be detached + // prior to deletion. + SFilterDetachTarget(SourceDeviceObject, TargetDeviceObject, PtrDeviceExtension); + + return; +} + + + +/************************************************************************* +* +* Function: SFilterFastIoQueryNetInfo() +* +* Description: +* Get information requested by a redirector across the network. This call +* will typically originate from the LAN Manager server. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFilterFastIoQueryNetInfo( +IN PFILE_OBJECT FileObject, +IN BOOLEAN Wait, +OUT PFILE_NETWORK_OPEN_INFORMATION Buffer, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN RC = FALSE; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + + try { + + // Get a pointer to the device extension. Must be ours. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(DeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // If the device extension is for a lower device forward the request. + if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) { + PFAST_IO_DISPATCH PtrFastIoDispatch = NULL; + + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + try { + // Just to synchronize with the attach and detach operations. + if (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED) { + PtrFastIoDispatch = PtrDeviceExtension->TargetDriverObject->FastIoDispatch; + } + } finally { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + } + + // Are we attached to a device that handles the larger fast-IO + // structure? Note that there are bigger problems if it does not + // but we should perform the check regardless. + if (PtrFastIoDispatch && (PtrFastIoDispatch->SizeOfFastIoDispatch + >= sizeof(FAST_IO_DISPATCH)) && (PtrFastIoDispatch->FastIoQueryNetworkOpenInfo)) { + RC = PtrFastIoDispatch->FastIoQueryNetworkOpenInfo(FileObject, + Wait, Buffer, IoStatus, + PtrDeviceExtension->TargetDeviceObject); + } + try_return(RC); + } + + try_exit: NOTHING; + + } finally { + ; + } + + return(RC); +} + + +/************************************************************************* +* +* Function: SFilterFastIoMdlRead() +* +* Description: +* Bypass the traditional IRP method to perform a MDL read operation. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFilterFastIoMdlRead( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN ULONG Length, +IN ULONG LockKey, +OUT PMDL *MdlChain, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN RC = FALSE; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + + try { + + // Get a pointer to the device extension. Must be ours. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(DeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // If the device extension is for a lower device forward the request. + if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) { + PFAST_IO_DISPATCH PtrFastIoDispatch = NULL; + + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + try { + // Just to synchronize with the attach and detach operations. + if (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED) { + PtrFastIoDispatch = PtrDeviceExtension->TargetDriverObject->FastIoDispatch; + } + } finally { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + } + + // Are we attached to a device that handles the larger fast-IO + // structure? Note that there are bigger problems if it does not + // but we should perform the check regardless. + if (PtrFastIoDispatch && (PtrFastIoDispatch->SizeOfFastIoDispatch + >= sizeof(FAST_IO_DISPATCH)) && (PtrFastIoDispatch->MdlRead)) { + RC = PtrFastIoDispatch->MdlRead(FileObject, FileOffset, Length, + LockKey, MdlChain, IoStatus, + PtrDeviceExtension->TargetDeviceObject); + } + try_return(RC); + } + + try_exit: NOTHING; + + } finally { + ; + } + + return(RC); +} + + +/************************************************************************* +* +* Function: SFilterFastIoMdlReadComplete() +* +* Description: +* Bypass the traditional IRP method to inform the NT Cache Manager and the +* FSD that the caller no longer requires the data locked in the system cache +* or the MDL to stay around anymore .. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFilterFastIoMdlReadComplete( +IN PFILE_OBJECT FileObject, +OUT PMDL MdlChain, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN RC = FALSE; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + + try { + + // Get a pointer to the device extension. Must be ours. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(DeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // If the device extension is for a lower device forward the request. + if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) { + PFAST_IO_DISPATCH PtrFastIoDispatch = NULL; + + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + try { + // Just to synchronize with the attach and detach operations. + if (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED) { + PtrFastIoDispatch = PtrDeviceExtension->TargetDriverObject->FastIoDispatch; + } + } finally { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + } + + // Are we attached to a device that handles the larger fast-IO + // structure? Note that there are bigger problems if it does not + // but we should perform the check regardless. + if (PtrFastIoDispatch && (PtrFastIoDispatch->SizeOfFastIoDispatch >= sizeof(FAST_IO_DISPATCH)) && + (PtrFastIoDispatch->MdlReadComplete)) { + RC = PtrFastIoDispatch->MdlReadComplete(FileObject, MdlChain, PtrDeviceExtension->TargetDeviceObject); + } + try_return(RC); + } + + try_exit: NOTHING; + + } finally { + ; + } + + return(RC); +} + + +/************************************************************************* +* +* Function: SFilterFastIoPrepareMdlWrite() +* +* Description: +* Bypass the traditional IRP method to prepare for a MDL write operation. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFilterFastIoPrepareMdlWrite( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN ULONG Length, +IN ULONG LockKey, +OUT PMDL *MdlChain, +OUT PIO_STATUS_BLOCK IoStatus, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN RC = FALSE; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + + try { + + // Get a pointer to the device extension. Must be ours. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(DeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // If the device extension is for a lower device forward the request. + if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) { + PFAST_IO_DISPATCH PtrFastIoDispatch = NULL; + + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + try { + // Just to synchronize with the attach and detach operations. + if (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED) { + PtrFastIoDispatch = PtrDeviceExtension->TargetDriverObject->FastIoDispatch; + } + } finally { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + } + + // Are we attached to a device that handles the larger fast-IO + // structure? Note that there are bigger problems if it does not + // but we should perform the check regardless. + if (PtrFastIoDispatch && (PtrFastIoDispatch->SizeOfFastIoDispatch >= sizeof(FAST_IO_DISPATCH)) && + (PtrFastIoDispatch->PrepareMdlWrite)) { + RC = PtrFastIoDispatch->PrepareMdlWrite(FileObject, FileOffset, + Length, LockKey, MdlChain, IoStatus, + PtrDeviceExtension->TargetDeviceObject); + } + try_return(RC); + } + + try_exit: NOTHING; + + } finally { + ; + } + + return(RC); +} + + +/************************************************************************* +* +* Function: SFilterFastIoMdlWriteComplete() +* +* Description: +* Bypass the traditional IRP method to inform the NT Cache Manager and the +* FSD that the caller has updated the contents of the MDL. This data can +* now be asynchronously written out to secondary storage by the Cache Mgr. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFilterFastIoMdlWriteComplete( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +OUT PMDL MdlChain, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN RC = FALSE; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + + try { + + // Get a pointer to the device extension. Must be ours. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(DeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // If the device extension is for a lower device forward the request. + if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) { + PFAST_IO_DISPATCH PtrFastIoDispatch = NULL; + + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + try { + // Just to synchronize with the attach and detach operations. + if (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED) { + PtrFastIoDispatch = PtrDeviceExtension->TargetDriverObject->FastIoDispatch; + } + } finally { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + } + + // Are we attached to a device that handles the larger fast-IO + // structure? Note that there are bigger problems if it does not + // but we should perform the check regardless. + if (PtrFastIoDispatch && (PtrFastIoDispatch->SizeOfFastIoDispatch >= sizeof(FAST_IO_DISPATCH)) && + (PtrFastIoDispatch->MdlWriteComplete)) { + RC = PtrFastIoDispatch->MdlWriteComplete(FileObject, FileOffset, MdlChain, PtrDeviceExtension->TargetDeviceObject); + } + try_return(RC); + } + + try_exit: NOTHING; + + } finally { + ; + } + + return(RC); +} + + + +/************************************************************************* +* +* Function: SFilterFastIoReadCompressed() +* +* Description: +* Bypass the traditional IRP method to perform a compressed read operation. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFilterFastIoReadCompressed( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN ULONG Length, +IN ULONG LockKey, +OUT PVOID Buffer, +OUT PMDL *MdlChain, +OUT PIO_STATUS_BLOCK IoStatus, +OUT struct _COMPRESSED_DATA_INFO *CompressedDataInfo, +IN ULONG CompressedDataInfoLength, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN RC = FALSE; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + + try { + + // Get a pointer to the device extension. Must be ours. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(DeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // If the device extension is for a lower device forward the request. + if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) { + PFAST_IO_DISPATCH PtrFastIoDispatch = NULL; + + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + try { + // Just to synchronize with the attach and detach operations. + if (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED) { + PtrFastIoDispatch = PtrDeviceExtension->TargetDriverObject->FastIoDispatch; + } + } finally { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + } + + if (PtrFastIoDispatch && PtrFastIoDispatch->FastIoReadCompressed) { + RC = PtrFastIoDispatch->FastIoReadCompressed(FileObject, FileOffset, Length, LockKey, Buffer, MdlChain, + IoStatus, CompressedDataInfo, CompressedDataInfoLength, + PtrDeviceExtension->TargetDeviceObject); + } + try_return(RC); + } + + // Otherwise, we return FALSE to indicate that the operation cannot + // be performed via the fast path. + + try_exit: NOTHING; + + } finally { + ; + } + + return(RC); +} + + + +/************************************************************************* +* +* Function: SFilterFastIoWriteCompressed() +* +* Description: +* Bypass the traditional IRP method to perform a compressed write operation. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFilterFastIoWriteCompressed( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +IN ULONG Length, +IN ULONG LockKey, +OUT PVOID Buffer, +OUT PMDL *MdlChain, +OUT PIO_STATUS_BLOCK IoStatus, +OUT struct _COMPRESSED_DATA_INFO *CompressedDataInfo, +IN ULONG CompressedDataInfoLength, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN RC = FALSE; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + + try { + + // Get a pointer to the device extension. Must be ours. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(DeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // If the device extension is for a lower device forward the request. + if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) { + PFAST_IO_DISPATCH PtrFastIoDispatch = NULL; + + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + try { + // Just to synchronize with the attach and detach operations. + if (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED) { + PtrFastIoDispatch = PtrDeviceExtension->TargetDriverObject->FastIoDispatch; + } + } finally { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + } + + if (PtrFastIoDispatch && PtrFastIoDispatch->FastIoWriteCompressed) { + RC = PtrFastIoDispatch->FastIoWriteCompressed(FileObject, FileOffset, Length, LockKey, Buffer, MdlChain, + IoStatus, CompressedDataInfo, CompressedDataInfoLength, + PtrDeviceExtension->TargetDeviceObject); + } + try_return(RC); + } + + // Otherwise, we return FALSE to indicate that the operation cannot + // be performed via the fast path. + + try_exit: NOTHING; + + } finally { + ; + } + + return(RC); +} + + +/************************************************************************* +* +* Function: SFilterFastIoMdlReadCompleteCompressed() +* +* Description: +* Bypass the traditional IRP method to inform the NT Cache Manager and the +* FSD that the caller no longer requires the data locked in the system cache +* or the MDL to stay around anymore .. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFilterFastIoMdlReadCompleteCompressed( +IN PFILE_OBJECT FileObject, +OUT PMDL MdlChain, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN RC = FALSE; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + + try { + + // Get a pointer to the device extension. Must be ours. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(DeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // If the device extension is for a lower device forward the request. + if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) { + PFAST_IO_DISPATCH PtrFastIoDispatch = NULL; + + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + try { + // Just to synchronize with the attach and detach operations. + if (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED) { + PtrFastIoDispatch = PtrDeviceExtension->TargetDriverObject->FastIoDispatch; + } + } finally { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + } + + // Are we attached to a device that handles the larger fast-IO + // structure? Note that there are bigger problems if it does not + // but we should perform the check regardless. + if (PtrFastIoDispatch && (PtrFastIoDispatch->SizeOfFastIoDispatch >= sizeof(FAST_IO_DISPATCH)) && + (PtrFastIoDispatch->MdlReadCompleteCompressed)) { + RC = PtrFastIoDispatch->MdlReadCompleteCompressed(FileObject, MdlChain, PtrDeviceExtension->TargetDeviceObject); + } + try_return(RC); + } + + try_exit: NOTHING; + + } finally { + ; + } + + return(RC); +} + + + +/************************************************************************* +* +* Function: SFilterFastIoMdlWriteCompleteCompressed() +* +* Description: +* Bypass the traditional IRP method to inform the NT Cache Manager and the +* FSD that the caller has updated the contents of the MDL. This data can +* now be asynchronously written out to secondary storage by the Cache Mgr. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: TRUE/FALSE +* +*************************************************************************/ +BOOLEAN SFilterFastIoMdlWriteCompleteCompressed( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER FileOffset, +OUT PMDL MdlChain, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN RC = FALSE; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + + try { + + // Get a pointer to the device extension. Must be ours. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(DeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // If the device extension is for a lower device forward the request. + if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) { + PFAST_IO_DISPATCH PtrFastIoDispatch = NULL; + + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + try { + // Just to synchronize with the attach and detach operations. + if (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED) { + PtrFastIoDispatch = PtrDeviceExtension->TargetDriverObject->FastIoDispatch; + } + } finally { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + } + + // Are we attached to a device that handles the larger fast-IO + // structure? Note that there are bigger problems if it does not + // but we should perform the check regardless. + if (PtrFastIoDispatch && (PtrFastIoDispatch->SizeOfFastIoDispatch >= sizeof(FAST_IO_DISPATCH)) && + (PtrFastIoDispatch->MdlWriteCompleteCompressed)) { + RC = PtrFastIoDispatch->MdlWriteCompleteCompressed(FileObject, FileOffset, MdlChain, + PtrDeviceExtension->TargetDeviceObject); + } + try_return(RC); + } + + try_exit: NOTHING; + + } finally { + ; + } + + return(RC); +} + + + +/************************************************************************* +* +* Function: SFilterFastIoAcqModWrite() +* +* Description: +* Not really a fast-io operation. Used by the VMM to acquire FSD resources +* before initiating a write operation via the Modified Page/Block Writer. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFilterFastIoAcqModWrite( +IN PFILE_OBJECT FileObject, +IN PLARGE_INTEGER EndingOffset, +OUT PERESOURCE *ResourceToRelease, +IN PDEVICE_OBJECT DeviceObject) +{ + NTSTATUS RC = STATUS_SUCCESS; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + + try { + + // Get a pointer to the device extension. Must be ours. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(DeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // If the device extension is for a lower device forward the request. + if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) { + PFAST_IO_DISPATCH PtrFastIoDispatch = NULL; + + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + try { + // Just to synchronize with the attach and detach operations. + if (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED) { + PtrFastIoDispatch = PtrDeviceExtension->TargetDriverObject->FastIoDispatch; + } + } finally { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + } + + // Are we attached to a device that handles the larger fast-IO + // structure? Note that there are bigger problems if it does not + // but we should perform the check regardless. + if (PtrFastIoDispatch && (PtrFastIoDispatch->SizeOfFastIoDispatch >= sizeof(FAST_IO_DISPATCH)) && + (PtrFastIoDispatch->AcquireForModWrite)) { + RC = PtrFastIoDispatch->AcquireForModWrite(FileObject, EndingOffset, ResourceToRelease, + PtrDeviceExtension->TargetDeviceObject); + } + try_return(RC); + } + + try_exit: NOTHING; + + } finally { + ; + } + + return(RC); +} + + +/************************************************************************* +* +* Function: SFilterFastIoRelModWrite() +* +* Description: +* Not really a fast-io operation. Used by the VMM to release FSD resources +* after processing a modified page/block write operation. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFilterFastIoRelModWrite( +IN PFILE_OBJECT FileObject, +IN PERESOURCE ResourceToRelease, +IN PDEVICE_OBJECT DeviceObject) +{ + NTSTATUS RC = STATUS_SUCCESS; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + + try { + + // Get a pointer to the device extension. Must be ours. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(DeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // If the device extension is for a lower device forward the request. + if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) { + PFAST_IO_DISPATCH PtrFastIoDispatch = NULL; + + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + try { + // Just to synchronize with the attach and detach operations. + if (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED) { + PtrFastIoDispatch = PtrDeviceExtension->TargetDriverObject->FastIoDispatch; + } + } finally { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + } + + // Are we attached to a device that handles the larger fast-IO + // structure? Note that there are bigger problems if it does not + // but we should perform the check regardless. + if (PtrFastIoDispatch && (PtrFastIoDispatch->SizeOfFastIoDispatch >= sizeof(FAST_IO_DISPATCH)) && + (PtrFastIoDispatch->ReleaseForModWrite)) { + RC = PtrFastIoDispatch->ReleaseForModWrite(FileObject, ResourceToRelease, PtrDeviceExtension->TargetDeviceObject); + } + try_return(RC); + } + + try_exit: NOTHING; + + } finally { + ; + } + + return(RC); +} + + +/************************************************************************* +* +* Function: SFilterFastIoAcqCcFlush() +* +* Description: +* Not really a fast-io operation. Used by the NT Cache Mgr to acquire FSD +* resources before performing a CcFlush() operation on a specific file +* stream. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFilterFastIoAcqCcFlush( +IN PFILE_OBJECT FileObject, +IN PDEVICE_OBJECT DeviceObject) +{ + NTSTATUS RC = STATUS_SUCCESS; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + + try { + + // Get a pointer to the device extension. Must be ours. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(DeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // If the device extension is for a lower device forward the request. + if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) { + PFAST_IO_DISPATCH PtrFastIoDispatch = NULL; + + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + try { + // Just to synchronize with the attach and detach operations. + if (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED) { + PtrFastIoDispatch = PtrDeviceExtension->TargetDriverObject->FastIoDispatch; + } + } finally { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + } + + // Are we attached to a device that handles the larger fast-IO + // structure? Note that there are bigger problems if it does not + // but we should perform the check regardless. + if (PtrFastIoDispatch && (PtrFastIoDispatch->SizeOfFastIoDispatch >= sizeof(FAST_IO_DISPATCH)) && + (PtrFastIoDispatch->AcquireForCcFlush)) { + RC = PtrFastIoDispatch->AcquireForCcFlush(FileObject, PtrDeviceExtension->TargetDeviceObject); + } + try_return(RC); + } + + try_exit: NOTHING; + + } finally { + ; + } + + return(RC); +} + + +/************************************************************************* +* +* Function: SFilterFastIoRelCcFlush() +* +* Description: +* Not really a fast-io operation. Used by the NT Cache Mgr to release FSD +* resources after performing a CcFlush() operation on a specific file +* stream. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFilterFastIoRelCcFlush( +IN PFILE_OBJECT FileObject, +IN PDEVICE_OBJECT DeviceObject) +{ + NTSTATUS RC = STATUS_SUCCESS; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + + try { + + // Get a pointer to the device extension. Must be ours. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(DeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // If the device extension is for a lower device forward the request. + if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) { + PFAST_IO_DISPATCH PtrFastIoDispatch = NULL; + + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + try { + // Just to synchronize with the attach and detach operations. + if (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED) { + PtrFastIoDispatch = PtrDeviceExtension->TargetDriverObject->FastIoDispatch; + } + } finally { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + } + + // Are we attached to a device that handles the larger fast-IO + // structure? Note that there are bigger problems if it does not + // but we should perform the check regardless. + if (PtrFastIoDispatch && (PtrFastIoDispatch->SizeOfFastIoDispatch >= sizeof(FAST_IO_DISPATCH)) && + (PtrFastIoDispatch->ReleaseForCcFlush)) { + RC = PtrFastIoDispatch->ReleaseForCcFlush(FileObject, PtrDeviceExtension->TargetDeviceObject); + } + try_return(RC); + } + + try_exit: NOTHING; + + } finally { + ; + } + + return(RC); +} + + +/************************************************************************* +* +* Function: SFilterFastIoQueryOpen() +* +* Description: +* Get file information (IRP is sent down!). +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +BOOLEAN SFilterFastIoQueryOpen( +IN PIRP Irp, +OUT PFILE_NETWORK_OPEN_INFORMATION NetworkInformation, +IN PDEVICE_OBJECT DeviceObject) +{ + BOOLEAN RC = FALSE; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + PIO_STACK_LOCATION PtrNextIoStackLocation = NULL; + PIO_STACK_LOCATION PtrCurrentStackLocation = NULL; + + try { + + // Get a pointer to the device extension. Must be ours. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(DeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // If the device extension is for a lower device forward the request. + if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) { + PFAST_IO_DISPATCH PtrFastIoDispatch = NULL; + + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + try { + // Just to synchronize with the attach and detach operations. + if (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED) { + PtrFastIoDispatch = PtrDeviceExtension->TargetDriverObject->FastIoDispatch; + } + } finally { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + } + + // Are we attached to a device that handles the larger fast-IO + // structure? Note that there are bigger problems if it does not + // but we should perform the check regardless. + if (PtrFastIoDispatch && (PtrFastIoDispatch->SizeOfFastIoDispatch >= sizeof(FAST_IO_DISPATCH)) && + (PtrFastIoDispatch->FastIoQueryOpen)) { + // update the device object ptr in the IRP (since we will not use IoCallDriver()). + + PtrCurrentStackLocation = IoGetCurrentIrpStackLocation(Irp); + PtrCurrentStackLocation->DeviceObject = PtrDeviceExtension->TargetDeviceObject; + + KdPrint(("SFilterFastIoQueryOpen(): Target device = 0x%x\n", + PtrDeviceExtension->TargetDeviceObject)); + + RC = PtrFastIoDispatch->FastIoQueryOpen(Irp, NetworkInformation, PtrDeviceExtension->TargetDeviceObject); + } + try_return(RC); + } + + try_exit: NOTHING; + + } finally { + ; + } + + return(RC); +} + +#endif //_WIN32_WINNT >= 0x0400 + + +``` + +`Windows Driver Development/Windows NT File System Internals/filter-high/src/fsctrl.c`: + +```c +/************************************************************************* +* +* File: fsctrl.c +* +* Module: Sample Filter Driver (Kernel mode execution only) +* +* Description: +* File System Control Requests entry point. +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +#include "sfilter.h" + +/* define the file specific bug-check id */ +#define SFILTER_BUG_CHECK_ID SFILTER_FILE_FSCTRL + + +/************************************************************************* +* +* Function: SFilterFSControl() +* +* Description: +* Intercept the fsctrl request targeted to the FSD. If you wish to +* have a user-space application (or kernel-mode driver) communicate +* with the filter driver, this is where your FSCTL requests would also +* end up. +* +* Expected Interrupt Level (for execution) : +* +* Typically (though not guaranteed) @ IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error +* +*************************************************************************/ +NTSTATUS SFilterFSControl( +PDEVICE_OBJECT DeviceObject, // Our device object +PIRP Irp) // I/O Request Packet +{ + NTSTATUS RC = STATUS_SUCCESS; + PIO_STACK_LOCATION PtrNextIoStackLocation = NULL; + PIO_STACK_LOCATION PtrCurrentStackLocation = NULL; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + BOOLEAN AcquiredExtension = FALSE; + BOOLEAN CompleteIrp = FALSE; + PDEVICE_OBJECT PtrTargetDeviceObject = NULL; + ULONG ReturnedInformation = 0; + PtrSFilterMountCompletion PtrMountCompletion = NULL; + + try { + + // Get the current I/O stack location. + PtrCurrentStackLocation = IoGetCurrentIrpStackLocation(Irp); + ASSERT(PtrCurrentStackLocation); + + // Get a pointer to the device extension that must exist for + // all of the device objects created by the filter driver. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(DeviceObject->DeviceExtension); + SFilterAssertExtPtrValid(PtrDeviceExtension); + + // We really should not be here if our resource is not initialized? + ASSERT(PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_RESOURCE_INITIALIZED); + + // acquire the resource associated with particular device extension for + // our target device object. + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + AcquiredExtension = TRUE; + + // If the device extension is for a lower device forward the request. + if ((PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_ATTACHED_DEVICE) + && (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED)) { + + // If we wish to intercept the mount volume, we would definitely like to + // set a different completion routine. Here is the tedious way + // of doing this (you could modify the common dispatch routine to + // do the same). + if (PtrCurrentStackLocation->MinorFunction != IRP_MN_MOUNT_VOLUME) { + if (AcquiredExtension) { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + AcquiredExtension = FALSE; + } + + // Note that a load FSD request could be being issued to a FS recognizer. + // If you wish, you can monitor such requests and detach yourself from + // the recognizer once the FSD has been loaded. That would allow the + // recognizer to be unloaded. + // This filter does not implement this behavior, however. + + RC = SFilterDefaultDispatch(DeviceObject, Irp); + try_return(RC); + } else { + + // Mount request issued. + KdPrint(("SFilterFSControl(): Mount request intercepted. Target FSD = 0x%x\n", + PtrDeviceExtension->TargetDriverObject)); + + // Be careful about not screwing up badly. This is actually not recommended by the I/O Manager. + if (Irp->CurrentLocation == 1) { + // Bad!! Fudge the error code. Break if we can ... + SFilterBreakPoint(); + CompleteIrp = TRUE; + try_return(RC = STATUS_INVALID_DEVICE_REQUEST); + } + + PtrNextIoStackLocation = IoGetNextIrpStackLocation(Irp); + + // So far, so good! Copy over the contents of the current stack + // location into the next IRP stack location. Be careful about + // how we do the copy. The following statement is convenient + // but will end up screwing up any driver above us who has + // set a completion routine! + *PtrNextIoStackLocation = *PtrCurrentStackLocation; + + // We need to allocate memory for the context structure. The information + // that we will pass on to the mount completion routine must contain + // the following: + // (a) A pointer to the device extension + // (b) A pointer to the VPB structure that we can currently access + // in the current I/O stack location. + // (c) A work queue item that can be used to post the attach request + // if the mount is successfull. + // The structure will be freed once the post-processing is completed. + PtrMountCompletion = (PtrSFilterMountCompletion)ExAllocatePool(NonPagedPool, sizeof(SFilterMountCompletion)); + if (!PtrMountCompletion) { + CompleteIrp = TRUE; + try_return(RC = STATUS_INSUFFICIENT_RESOURCES); + } + RtlZeroMemory(PtrMountCompletion, sizeof(SFilterMountCompletion)); + PtrMountCompletion->NodeIdentifier.NodeType = SFILTER_NODE_TYPE_MOUNT_COMPLETION; + PtrMountCompletion->NodeIdentifier.NodeSize = sizeof(SFilterMountCompletion); + PtrMountCompletion->PtrOriginalIrp = Irp; + // We will store a pointer to the "real" device object. We can use the + // VPB associated with the real device object once the mount operation + // has completed. This *may* be the same VPB structure as the one passed in + // by the I/O Manager to the FSD. However, it is possible that a "remount" + // operation will be performed by the FSD in which case the FSD will replace + // the VPB pointer passed-in by the I/O Manager with the "old" VPB pointer + // that was already associated with the "real device object". + // Bottom line is that we do not really know the "VPB" pointer until the + // the FSD has processed the mount request (note that if this is NOT a + // remount, the FSD will make the real device VPB pointer be the VPB pointer + // passed in by the I/O Manager). + PtrMountCompletion->PtrDeviceObject = PtrCurrentStackLocation->Parameters.MountVolume.Vpb->RealDevice; + + PtrMountCompletion->PtrDeviceExtension = PtrDeviceExtension; + // might as well initialize the work item now ... + ExInitializeWorkItem(&(PtrMountCompletion->WorkItem), SFilterMountAttach, PtrMountCompletion); + + // We will specify a default completion routine. This will + // prevent any completion routine being invoked twice + // (set by a driver above us in the calling hierarchy) and also + // allow us the opportunity to do whatever we like once the + // function processing has been completed. + // We will specify that our completion routine be invoked regardless + // of how the IRP is completed/cancelled. + IoSetCompletionRoutine(Irp, SFilterMountVolumeCompletion, PtrMountCompletion, TRUE, TRUE, TRUE); + + // Forward the request. Note that if the target does not + // wish to service the function, the request will get redirected + // to IopInvalidDeviceRequest() (a routine that completes the + // IRP with STATUS_INVALID_DEVICE_REQUEST). + // However, we must release our resources before forwarding the + // request. That will avoid the sort of problems discussed in + // Chapter 12 of the text. + + PtrTargetDeviceObject = PtrDeviceExtension->TargetDeviceObject; + + // Increment the count of outstanding I/O requests. The count will + // be decremented in the completion routine. + // Acquire a special end-resource spin-lock to synchronize access. + SFilterIncrementLargeInteger(PtrDeviceExtension->OutstandingIoRequests, + (unsigned long)1, + &(PtrDeviceExtension->IoRequestsSpinLock)); + + // Clear the fast-IO notification event protected by the resource + // we have acquired. + KeClearEvent(&(PtrDeviceExtension->IoInProgressEvent)); + + if (AcquiredExtension) { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + AcquiredExtension = FALSE; + } + + // Note that we will mark the IRP Pending and will return + // STATUS_MORE_PROCESSING_REQUIRED later in the completion + // routine. + + IoMarkIrpPending(Irp); + + RC = IoCallDriver(PtrTargetDeviceObject, Irp); + + KdPrint(("SFilterFSControl(): Mount request intercepted. Target FSD Returned = 0x%x\n", RC)); + + try_return(RC = STATUS_PENDING); + } + } else if (PtrDeviceExtension->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_FILTER_DEVICE) { + // This request was targeted to us. You may allow such requests + // if you expect other drivers/applications created/developed by + // you or others to be able to issue IOCTL requests to your driver. + // Currently, I expect no such thing. + CompleteIrp = TRUE; + try_return(RC = STATUS_INVALID_DEVICE_REQUEST); + } + + // The filter driver does not particularly care to respond to these requests. + CompleteIrp = TRUE; + // does not matter what the Information field contains. + try_return(RC = STATUS_INVALID_DEVICE_REQUEST); + + try_exit: NOTHING; + + } finally { + + // Release any resources acquired. + if (AcquiredExtension) { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + AcquiredExtension = FALSE; + } + + // Complete the IRP only if we must. + if (CompleteIrp) { + Irp->IoStatus.Status = RC; + Irp->IoStatus.Information = ReturnedInformation; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + } + } + + return(RC); +} + +/************************************************************************* +* +* Function: SFilterMountVolumeCompletion() +* +* Description: +* Return STATUS_MORE_PROCESSING_REQUIRED if the request +* was successfull (mount happened). Else, let it go. +* +* Expected Interrupt Level (for execution) : +* +* Typically at high IRQL. Occasionally at IRQL PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/STATUS_MORE_PROCESSING_REQUIRED +* +*************************************************************************/ +NTSTATUS SFilterMountVolumeCompletion( +PDEVICE_OBJECT PtrDeviceObject, +PIRP PtrIrp, +void *Context) +{ + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + BOOLEAN CanDetachProceed = FALSE; + PDEVICE_OBJECT PtrAssociatedDeviceObject = NULL; + PtrSFilterMountCompletion PtrMountCompletion = NULL; + + PtrMountCompletion = (PtrSFilterMountCompletion)(Context); + ASSERT(PtrMountCompletion); + ASSERT(PtrMountCompletion->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_MOUNT_COMPLETION); + ASSERT(PtrMountCompletion->NodeIdentifier.NodeSize = sizeof(SFilterMountCompletion)); + + PtrDeviceExtension = PtrMountCompletion->PtrDeviceExtension; + ASSERT(PtrDeviceExtension); + + if (PtrIrp->PendingReturned) { + IoMarkIrpPending(PtrIrp); + } + + // Get a pointer to the VPB that is the "real" VPB (whether a fresh mount + // was performed or whether a real mount happened). + PtrMountCompletion->PtrVPB = PtrMountCompletion->PtrDeviceObject->Vpb; + + PtrAssociatedDeviceObject = PtrDeviceExtension->PtrAssociatedDeviceObject; + + // Ensure that this is a valid device object pointer, else return + // immediately. + if (PtrAssociatedDeviceObject != PtrDeviceObject) { + // Bug exposed; see Chapter 12 for details. + return(STATUS_SUCCESS); + } + + if (NT_SUCCESS(PtrIrp->IoStatus.Status)) { + // Mount succeeded. We will process the remainder asynchronously + // and will therefore return STATUS_MORE_PROCESSING_REQUIRED. + + // post the request and return. + ExQueueWorkItem(&(PtrMountCompletion->WorkItem), CriticalWorkQueue); + + return(STATUS_MORE_PROCESSING_REQUIRED); + } else { + // who cares; could be that a FSD will be loaded later? + SFilterDecrementLargeInteger(PtrDeviceExtension->OutstandingIoRequests, + (unsigned long)1, + &(PtrDeviceExtension->IoRequestsSpinLock)); + + // If the outstanding count is 0, signal the appropriate event which will + // allow any pending detach to proceed. + SFilterIsLargeIntegerZero(CanDetachProceed, PtrDeviceExtension->OutstandingIoRequests, + &(PtrDeviceExtension->IoRequestsSpinLock)); + + if (CanDetachProceed) { + // signal the event object. Note that this is simply an + // advisory check we do here (to wake up a sleeping thread). + // It is the responsibility of the thread performing the detach to + // ensure that no operations are truly in progress. + KeSetEvent(&(PtrDeviceExtension->IoInProgressEvent), IO_NO_INCREMENT, FALSE); + } + + // free up the memory for the mount completion structure + ExFreePool((void *)PtrMountCompletion); + + return(STATUS_SUCCESS); + } +} + + +/************************************************************************* +* +* Function: SFilterMountAttach() +* +* Description: +* Attach to the newly created device object representing the +* mounted logical volume. Then complete the original IRP. +* +* Expected Interrupt Level (for execution) : +* +* At IRQL PASSIVE_LEVEL +* +* Return Value: None. +* +*************************************************************************/ +void SFilterMountAttach( +void *Context) +{ + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + PtrSFilterDeviceExtension PtrNewDeviceExtension = NULL; + BOOLEAN CanDetachProceed = FALSE; + PDEVICE_OBJECT PtrTargetVolumeDeviceObject = NULL; + PDEVICE_OBJECT PtrNewDeviceObject = NULL; + PtrSFilterMountCompletion PtrMountCompletion = NULL; + NTSTATUS RC = STATUS_SUCCESS; + + SFilterBreakPoint(); + + PtrMountCompletion = (PtrSFilterMountCompletion)(Context); + ASSERT(PtrMountCompletion); + ASSERT(PtrMountCompletion->NodeIdentifier.NodeType == SFILTER_NODE_TYPE_MOUNT_COMPLETION); + ASSERT(PtrMountCompletion->NodeIdentifier.NodeSize = sizeof(SFilterMountCompletion)); + + // Get a pointer to the target device object. + PtrTargetVolumeDeviceObject = PtrMountCompletion->PtrVPB->DeviceObject; + + PtrDeviceExtension = PtrMountCompletion->PtrDeviceExtension; + ASSERT(PtrDeviceExtension); + + // issue the attach request to the target device object. + RC = SFilterAttachTarget(PtrTargetVolumeDeviceObject, &PtrNewDeviceObject); + + KdPrint(("SFilterFSDNotification(): Result of attaching to device object 0x%x is 0x%x\n", + PtrTargetVolumeDeviceObject, RC)); + + // if we succeeded, remove the "attached-to-fsd" flag (sorry for the hack) + if (NT_SUCCESS(RC)) { + ASSERT(PtrNewDeviceObject); + + PtrNewDeviceExtension = (PtrSFilterDeviceExtension)(PtrNewDeviceObject->DeviceExtension); + + SFilterClearFlag(PtrNewDeviceExtension->DeviceExtensionFlags, SFILTER_DEV_EXT_ATTACHED_FSD); + } + + // we are free to detach now ?? + SFilterDecrementLargeInteger(PtrDeviceExtension->OutstandingIoRequests, + (unsigned long)1, + &(PtrDeviceExtension->IoRequestsSpinLock)); + + // If the outstanding count is 0, signal the appropriate event which will + // allow any pending detach to proceed. + SFilterIsLargeIntegerZero(CanDetachProceed, PtrDeviceExtension->OutstandingIoRequests, + &(PtrDeviceExtension->IoRequestsSpinLock)); + + if (CanDetachProceed) { + // signal the event object. Note that this is simply an + // advisory check we do here (to wake up a sleeping thread). + // It is the responsibility of the thread performing the detach to + // ensure that no operations are truly in progress. + KeSetEvent(&(PtrDeviceExtension->IoInProgressEvent), IO_NO_INCREMENT, FALSE); + } + + IoCompleteRequest(PtrMountCompletion->PtrOriginalIrp, IO_NO_INCREMENT); + + // free up the memory for the mount completion structure + ExFreePool((void *)PtrMountCompletion); + + return; +} + + +``` + +`Windows Driver Development/Windows NT File System Internals/filter-high/src/makefile`: + +``` +# +# DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source +# file to this component. This file merely indirects to the real make file +# that is shared by all the driver components of the Windows NT DDK +# + +!INCLUDE $(NTMAKEENV)\makefile.def + +``` + +`Windows Driver Development/Windows NT File System Internals/filter-high/src/misc.c`: + +```c +/************************************************************************* +* +* File: misc.c +* +* Module: Sample Filter Driver (Kernel mode execution only) +* +* Description: +* Contains miscelleneous functions. +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +#include "sfilter.h" + +/* define the file specific bug-check id */ +#define SFILTER_BUG_CHECK_ID SFILTER_FILE_MISC + + + +/************************************************************************* +* +* Function: SFilterInitDevExtension() +* +* Description: +* Performs some rudimentary work to initialize the device extension +* presumably for a newly created device object. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: None. +* +*************************************************************************/ +void SFilterInitDevExtension( +PtrSFilterDeviceExtension PtrDeviceExtension, +PDEVICE_OBJECT PtrAssociatedDeviceObject, +uint32 NodeType) +{ + RtlZeroMemory(PtrDeviceExtension, sizeof(SFilterDeviceExtension)); + + PtrDeviceExtension->NodeIdentifier.NodeType = NodeType; + PtrDeviceExtension->NodeIdentifier.NodeSize = sizeof(SFilterDeviceExtension); + + PtrDeviceExtension->PtrAssociatedDeviceObject = PtrAssociatedDeviceObject; + + ExInitializeResourceLite(&(PtrDeviceExtension->DeviceExtensionResource)); + // Set a flag indicating that the resource has been initialized. + SFilterSetFlag(PtrDeviceExtension->DeviceExtensionFlags, SFILTER_DEV_EXT_RESOURCE_INITIALIZED); + + try { + ExAcquireResourceExclusiveLite(&(SFilterGlobalData.GlobalDataResource), TRUE); + // Insert the object into the global list. + InsertTailList(&(SFilterGlobalData.NextDeviceObject), &(PtrDeviceExtension->NextDeviceObject)); + // Mark the fact that the device object has been inserted. + SFilterSetFlag(PtrDeviceExtension->DeviceExtensionFlags, SFILTER_DEV_EXT_INSERTED_GLOBAL_LIST); + } finally { + SFilterReleaseResource(&(SFilterGlobalData.GlobalDataResource)); + } + + // Initialize the Executive spin lock for this device extension. + KeInitializeSpinLock(&(PtrDeviceExtension->IoRequestsSpinLock)); + + // Initialize the event object used to denote I/O in progress. + // When set, the event signals that no I/O is currently in progress + // excluding (currently) fast-IO operations. + // Event is cleared in a safe fashion (protected by the resource + // initialized above) BUT is set in an unsafe fashion. Therefore, the + // waiting thread is responsible for rechecking that the condition + // is still true (after acquiring the resource) once it has been awoken. + KeInitializeEvent(&(PtrDeviceExtension->IoInProgressEvent), NotificationEvent, FALSE); + + return; +} + + +/************************************************************************* +* +* Function: SFilterDeleteDevExtension() +* +* Description: +* Performs some rudimentary work to uninitialize the device extension +* for an object presumably being deleted. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: None. +* +*************************************************************************/ +void SFilterDeleteDevExtension( +PtrSFilterDeviceExtension PtrDeviceExtension, +BOOLEAN ResourceAcquired) +{ + + // If the caller did not invoke this routine with the device extension + // resource acquired, we will acquire it here. + if (!ResourceAcquired && (PtrDeviceExtension->DeviceExtensionFlags & + SFILTER_DEV_EXT_RESOURCE_INITIALIZED)) { + ExAcquireResourceExclusiveLite(&(PtrDeviceExtension->DeviceExtensionResource), TRUE); + ResourceAcquired = TRUE; + } + + // assert that the device object associated with the + // extension to be deleted is not attached to anything. + ASSERT(!(PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_ATTACHED)); + + // Remove the device extension from the global list. + try { + ExAcquireResourceExclusiveLite(&(SFilterGlobalData.GlobalDataResource), TRUE); + if (PtrDeviceExtension->DeviceExtensionFlags & SFILTER_DEV_EXT_INSERTED_GLOBAL_LIST) { + RemoveEntryList(&(PtrDeviceExtension->NextDeviceObject)); + SFilterClearFlag(PtrDeviceExtension->DeviceExtensionFlags, SFILTER_DEV_EXT_INSERTED_GLOBAL_LIST); + } + } finally { + SFilterReleaseResource(&(SFilterGlobalData.GlobalDataResource)); + } + + if (ResourceAcquired) { + SFilterReleaseResource(&(PtrDeviceExtension->DeviceExtensionResource)); + } + + PtrDeviceExtension->NodeIdentifier.NodeType = 0; + PtrDeviceExtension->NodeIdentifier.NodeSize = 0; + + // Uninitialize the resource. + ExDeleteResourceLite(&(PtrDeviceExtension->DeviceExtensionResource)); + SFilterClearFlag(PtrDeviceExtension->DeviceExtensionFlags, SFILTER_DEV_EXT_RESOURCE_INITIALIZED); + + return; +} + + + + +/************************************************************************* +* +* Function: SFilterCreateDirectory() +* +* Description: +* Create a directory in the object name space. Make the directory +* a temporary object if requested by the caller. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/an appropriate error +* +*************************************************************************/ +NTSTATUS SFilterCreateDirectory( +PWCHAR DirectoryNameStr, +PHANDLE PtrReturnedHandle, +BOOLEAN MakeTemporaryObject) +{ + NTSTATUS RC = STATUS_SUCCESS; + UNICODE_STRING DirectoryName; + OBJECT_ATTRIBUTES DirectoryAttributes; + + try { + // Create a Unicode string. + RtlInitUnicodeString(&DirectoryName, DirectoryNameStr); + + // Create an object attributes structure. + InitializeObjectAttributes(&DirectoryAttributes, + &DirectoryName, OBJ_PERMANENT, + NULL, NULL); + + // The following call will fail if we do not have appropriate privileges. + if (!NT_SUCCESS(RC = ZwCreateDirectoryObject(PtrReturnedHandle, + DIRECTORY_ALL_ACCESS, + &DirectoryAttributes))) { + try_return(RC); + } + + // Make the named directory object a temporary object. + ZwMakeTemporaryObject(PtrReturnedHandle); + + try_exit: NOTHING; + + } finally { + NOTHING; + } + + return(RC); +} + + +/************************************************************************* +* +* Function: SFilterReinitialize() +* +* Description: +* You can read the registry if you like at this time, or do anything else +* that could not be done because the filter loads at system boot time. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: None +* +*************************************************************************/ +void SFilterReinitialize( +PDRIVER_OBJECT DriverObject, // representing the filter driver +void *Context, // this filter driver supplies registry path +ULONG Count) // # times this function invoked +{ + PUNICODE_STRING RegistryPath = NULL; + + // Currently, this function does not do anything + // (except break into a debugger if appropriate). You could, however, + // modify this function to suit your purposes (e.g. read the registry + // contents for user-configurable parameters) + + // Note that you can specify a reinitialization routine from within this + // routine to keep getting invoked whenever a new driver gets loaded. + // In this manner, you can check for known FSDs that load in versions + // previous to version 4.0 + + RegistryPath = (PUNICODE_STRING)(Context); + + return; +} + + +/************************************************************************* +* +* Function: SFilterFSDNotification +* +* Description: +* This function is invoked by the I/O Manager in version 4.0+ of the +* OS whenever a FSD registers or unregisters itself. +* This function could also be invoked internally by the filter driver +* from the reinitialization function for versions prior to 4.0. +* Currently, the filter driver will attach itself to the target device +* object when the function is invoked. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL (in the context of a system worker thread) +* +* Return Value: None +* +*************************************************************************/ +void SFilterFSDNotification( +PDEVICE_OBJECT PtrTargetFileSystemDeviceObject, +BOOLEAN DriverActive) +{ + NTSTATUS RC = STATUS_SUCCESS; + PDEVICE_OBJECT PtrNewDeviceObject = NULL; + + if (DriverActive) { + // Create a new device object and attach to the supplied device object. + // Then, we will intercept all requests to the FSD including (but not + // limited to) mount requests. Therefore, we will be able to attach to + // device objects created on-the-fly by the FSD representing instances + // of mounted logical volumes. + + RC = SFilterAttachTarget(PtrTargetFileSystemDeviceObject, &PtrNewDeviceObject); + + KdPrint(("SFilterFSDNotification(): Result of attaching to device object 0x%x is 0x%x\n", + PtrTargetFileSystemDeviceObject, RC)); + } else { + // Nothing much to do here. Remember that it will be a rare FSD indeed + // that is unloadable (though you can run into problems in versions 3.51 + // and before if the FSD indeed tries to unload itself). + // You may wish to add code here that traverses the filter driver's linked + // list of device objects attached to FSDs and delete the appropriate + // (attached) device object. + NOTHING; + } + + return; +} + + +``` + +`Windows Driver Development/Windows NT File System Internals/filter-high/src/sfilinit.c`: + +```c +/************************************************************************* +* +* File: sfilinit.c +* +* Module: Sample Filter Driver (Kernel mode execution only) +* +* Description: +* This file contains the initialization code for the kernel mode +* Sample Filter module. The DriverEntry() routine is called by the I/O +* sub-system to initialize the filter driver. The filter driver is +* designed to layer over FSD device objects. +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +// Common include file +#include "sfilter.h" + +// Define the file-specific bug-check id +#define SFILTER_BUG_CHECK_ID SFILTER_FILE_INIT + +// Global variables are declared here (minimize these) +SFilterData SFilterGlobalData; + + +/************************************************************************* +* +* Function: DriverEntry() +* +* Description: +* This routine is the standard entry point for most kernel mode drivers. +* The routine is invoked at IRQL PASSIVE_LEVEL in the context of a +* system worker thread. +* All Filter driver specific data structures etc. are initialized here. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error (will cause driver to be unloaded). +* +*************************************************************************/ +NTSTATUS DriverEntry( +PDRIVER_OBJECT DriverObject, // created by the I/O sub-system +PUNICODE_STRING RegistryPath) // path to the registry key +{ + NTSTATUS RC = STATUS_SUCCESS; + BOOLEAN RegisteredShutdown = FALSE; + PtrSFilterDeviceExtension PtrDeviceExtension = NULL; + + UNICODE_STRING UserVisibleName; + UNICODE_STRING DriverDeviceName; + + // Allows us to break into the filter driver (if required) at + // driver initialization (only with the debug/checked build). + SFilterBreakPoint(); + + try { + try { + // initialize the global data structure + RtlZeroMemory(&SFilterGlobalData, sizeof(SFilterGlobalData)); + + // initialize some required fields + SFilterGlobalData.NodeIdentifier.NodeType = SFILTER_NODE_TYPE_GLOBAL_DATA; + SFilterGlobalData.NodeIdentifier.NodeSize = sizeof(SFilterGlobalData); + + // initialize the global data resource and remember the fact that + // the resource has been initialized + RC = ExInitializeResourceLite(&(SFilterGlobalData.GlobalDataResource)); + ASSERT(NT_SUCCESS(RC)); + SFilterSetFlag(SFilterGlobalData.SFilterFlags, SFILTER_DATA_FLAGS_RESOURCE_INITIALIZED); + + // keep a ptr to the driver object sent to us by the I/O Mgr + SFilterGlobalData.SFilterDriverObject = DriverObject; + + // initialize the layered device object list head + InitializeListHead(&(SFilterGlobalData.NextDeviceObject)); + + // initialize the IRP major function table, and the fast I/O table + SFilterInitializeFunctionPointers(DriverObject); + + // It would be nice to create directories in which to create + // named (temporary) filter driver objects. + + // Start with a directory in the NT Object Name Space. + if (!NT_SUCCESS(RC = SFilterCreateDirectory(SFILTER_DRV_DIR, + &(SFilterGlobalData.DirectoryHandle), + TRUE))) { + SFilterBreakPoint(); + try_return(RC); + } + + // Now create a directory in the DOS (Win32) visible name space. + if (!NT_SUCCESS(RC = SFilterCreateDirectory(SFILTER_DOS_DRV_DIR, + &(SFilterGlobalData.DosDirectoryHandle), + TRUE))) { + SFilterBreakPoint(); + try_return(RC); + } + + // create a device object representing the driver itself + // so that requests can be targeted to the driver ... + RtlInitUnicodeString(&DriverDeviceName, SFILTER_DRV_NAME); + if (!NT_SUCCESS(RC = IoCreateDevice( + DriverObject, + sizeof(SFilterDeviceExtension), + &DriverDeviceName, + FILE_DEVICE_FILE_SYSTEM, // For lack of anything better ? + 0, + FALSE, // Not exclusive. + &(SFilterGlobalData.SFilterDeviceObject)))) { + // failed to create a device object, leave. + SFilterBreakPoint(); + try_return(RC); + } + + // Initialize the extension for the device object. + PtrDeviceExtension = (PtrSFilterDeviceExtension)(SFilterGlobalData.SFilterDeviceObject->DeviceExtension); + SFilterInitDevExtension(PtrDeviceExtension, SFilterGlobalData.SFilterDeviceObject, SFILTER_NODE_TYPE_FILTER_DEVICE); + + // In order to allow user-space helper applications to access our + // device object for the filter driver, create a symbolic link to + // the object. + RtlInitUnicodeString(&UserVisibleName, SFILTER_DOS_DRV_NAME); + if (!NT_SUCCESS(RC = IoCreateSymbolicLink(&UserVisibleName, &DriverDeviceName))) { + SFilterBreakPoint(); + try_return(RC); + } + SFilterSetFlag(SFilterGlobalData.SFilterFlags, SFILTER_DATA_FLAGS_SYMLINK_CREATED); + + + // The filter driver is not currently unloadable. However, you could + // extend the driver to support such functionality if so desired. + // You must be careful about any pending I/O requests, must unattach + // any previously attached device objects, release all resources. + // All of the above is not easy but certainly doable for someone + // sufficiently motivated :-) + +#if(_WIN32_WINNT >= 0x0400) + // For version 4.0+ of the operating system only: + // We will ask the I/O Manager to inform us whenever a FSD registers + // itself. We should be able to get to all native local file system drivers + // in this manner (and likely all network redirectors). + + // For versions prior to 4.0, see the reinitialization function + // specified by this filter driver. + RC = IoRegisterFsRegistrationChange(DriverObject, SFilterFSDNotification); +#endif //(_WIN32_WINNT >= 0x0400) + + } except (EXCEPTION_EXECUTE_HANDLER) { + // We encountered an exception somewhere, eat it up. + RC = GetExceptionCode(); + SFilterBreakPoint(); + } + + try_exit: NOTHING; + } finally { + // Start unwinding if we were unsuccessful. + if (!NT_SUCCESS(RC)) { + + if (SFilterGlobalData.SFilterFlags & SFILTER_DATA_FLAGS_SYMLINK_CREATED) { + IoDeleteSymbolicLink(&UserVisibleName); + } + + // Now, delete any device objects, etc. we may have created + if (SFilterGlobalData.SFilterDeviceObject) { + if (PtrDeviceExtension) { + SFilterDeleteDevExtension(PtrDeviceExtension, FALSE); + } + // nothing (no-one) should be attached to our device + // object at this time. + IoDeleteDevice(SFilterGlobalData.SFilterDeviceObject); + SFilterGlobalData.SFilterDeviceObject = NULL; + } + + // Delete the directories we may have created. + if (SFilterGlobalData.DirectoryHandle) { + ZwClose(SFilterGlobalData.DirectoryHandle); + SFilterGlobalData.DirectoryHandle = NULL; + } + + if (SFilterGlobalData.DosDirectoryHandle) { + ZwClose(SFilterGlobalData.DosDirectoryHandle); + SFilterGlobalData.DosDirectoryHandle = NULL; + } + + // delete the resource we may have initialized + if (SFilterGlobalData.SFilterFlags & SFILTER_DATA_FLAGS_RESOURCE_INITIALIZED) { + // uninitialize this resource + ExDeleteResourceLite(&(SFilterGlobalData.GlobalDataResource)); + SFilterClearFlag(SFilterGlobalData.SFilterFlags, SFILTER_DATA_FLAGS_RESOURCE_INITIALIZED); + } + } else { + // This driver is loaded at system boot time. Therefore, not all + // of the registry is available at that time. We will set a + // a reinitialization function that will allow us to read user-specified + // data. + // For versions previous to version 4.0, we can also attach to + // "known" FSDs in the reinitialization function (as long as the + // driver loads suitably late in the cycle). + IoRegisterDriverReinitialization(DriverObject, SFilterReinitialize, RegistryPath); + } + } + + return(RC); +} + + + +/************************************************************************* +* +* Function: SFilterInitializeFunctionPointers() +* +* Description: +* Initialize the IRP major function pointer array in the driver object +* structure. Also initialize the fast-IO function ptr array. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: None +* +*************************************************************************/ +void SFilterInitializeFunctionPointers( +PDRIVER_OBJECT DriverObject) // created by the I/O sub-system +{ + PFAST_IO_DISPATCH PtrFastIoDispatch = NULL; + unsigned int Index = 0; + + // You must initialize the function pointer table such that your + // filter driver supports all of the functions that the target + // would originally support unless you really want to prevent the user + // from being able to invoke a specific function. Even then, a better + // way to deny the specific functionality is to initialize the specific + // major function dispatch table entry to refer to your function and + // then return an error from within your dispatch function. + // Here, all I want to do is trap the read/write routines and let the + // others go through. + // However, create, cleanup, and close should also be appropriately + // initialized to allow your user-space helper application to open + // the device object representing the filter device. + + // First, initialize all function pointers to a default value. + for (Index = 0; Index <= IRP_MJ_MAXIMUM_FUNCTION; Index++) { + DriverObject->MajorFunction[Index] = SFilterDefaultDispatch; + } + + // Initialize the ones we care about with unique function pointers. + // Note that the functions your driver will care about will depend upon + // exactly what it is that your filter wishes to do. + // Currently, all we really care about is intercepting mount/verify/dismount + // requests. + DriverObject->MajorFunction[IRP_MJ_CREATE] = SFilterCreate; + DriverObject->MajorFunction[IRP_MJ_CLOSE] = SFilterCleanupOrClose; + DriverObject->MajorFunction[IRP_MJ_CLEANUP] = SFilterCleanupOrClose; + DriverObject->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL] = SFilterFSControl; + + // Now, it is time to initialize the fast-IO stuff. + PtrFastIoDispatch = DriverObject->FastIoDispatch = &(SFilterGlobalData.SFilterFastIoDispatch); + + // Initialize the global fast-IO structure. + // NOTE: The fast-IO structure has undergone a substantial revision + // in Windows NT Version 4.0. The structure has been extensively + // expanded. + // + // Therefore, if your driver needs to work on both V3.51 and V4.0+, + // you will have to be able to distinguish between the two versions + // at compile time. + PtrFastIoDispatch->SizeOfFastIoDispatch = sizeof(FAST_IO_DISPATCH); + PtrFastIoDispatch->FastIoCheckIfPossible = SFilterFastIoCheckIfPossible; + PtrFastIoDispatch->FastIoRead = SFilterFastIoRead; + PtrFastIoDispatch->FastIoWrite = SFilterFastIoWrite; + PtrFastIoDispatch->FastIoQueryBasicInfo = SFilterFastIoQueryBasicInfo; + PtrFastIoDispatch->FastIoQueryStandardInfo = SFilterFastIoQueryStdInfo; + PtrFastIoDispatch->FastIoLock = SFilterFastIoLock; + PtrFastIoDispatch->FastIoUnlockSingle = SFilterFastIoUnlockSingle; + PtrFastIoDispatch->FastIoUnlockAll = SFilterFastIoUnlockAll; + PtrFastIoDispatch->FastIoUnlockAllByKey = SFilterFastIoUnlockAllByKey; + PtrFastIoDispatch->FastIoDeviceControl = SFilterFastIoDeviceControl; + + PtrFastIoDispatch->AcquireFileForNtCreateSection = SFilterFastIoAcqCreateSec; + PtrFastIoDispatch->ReleaseFileForNtCreateSection = SFilterFastIoRelCreateSec; + + // The remaining are only valid under NT Version 4.0 and later. +#if(_WIN32_WINNT >= 0x0400) + PtrFastIoDispatch->FastIoDetachDevice = SFilterFastIoDetachDevice; + + PtrFastIoDispatch->FastIoQueryNetworkOpenInfo = SFilterFastIoQueryNetInfo; + + /* MDL functionality */ + PtrFastIoDispatch->MdlRead = SFilterFastIoMdlRead; + PtrFastIoDispatch->MdlReadComplete = SFilterFastIoMdlReadComplete; + PtrFastIoDispatch->PrepareMdlWrite = SFilterFastIoPrepareMdlWrite; + PtrFastIoDispatch->MdlWriteComplete = SFilterFastIoMdlWriteComplete; + PtrFastIoDispatch->FastIoReadCompressed = SFilterFastIoReadCompressed; + PtrFastIoDispatch->FastIoWriteCompressed = SFilterFastIoWriteCompressed; + PtrFastIoDispatch->MdlReadCompleteCompressed = + SFilterFastIoMdlReadCompleteCompressed; + PtrFastIoDispatch->MdlWriteCompleteCompressed = + SFilterFastIoMdlWriteCompleteCompressed; + + // The only fast-IO request (currently) that actually takes an IRP. + PtrFastIoDispatch->FastIoQueryOpen = SFilterFastIoQueryOpen; + + PtrFastIoDispatch->AcquireForModWrite = SFilterFastIoAcqModWrite; + PtrFastIoDispatch->ReleaseForModWrite = SFilterFastIoRelModWrite; + PtrFastIoDispatch->AcquireForCcFlush = SFilterFastIoAcqCcFlush; + PtrFastIoDispatch->ReleaseForCcFlush = SFilterFastIoRelCcFlush; +#endif //(_WIN32_WINNT >= 0x0400) + + return; +} + + +``` + +`Windows Driver Development/Windows NT File System Internals/filter-high/src/sfilter.rc`: + +```rc +#include + +#include + +#define VER_FILETYPE VFT_DRV +#define VER_FILESUBTYPE VFT2_DRV_SYSTEM +#define VER_FILEDESCRIPTION_STR "Sample Filter Driver" +#define VER_INTERNALNAME_STR "sfilter.sys" + +#include "common.ver" + +``` + +`Windows Driver Development/Windows NT File System Internals/filter-high/src/sources`: + +``` +# - Execute the "build" command to make the sample filter driver + +# The MAJORCOMP and MINORCOMP variables are defined +# so that $(MAJORCOMP)$(MINORCOMP)filename can be used in +# cross compiling to provide unique filenames in a flat namespace. + +MAJORCOMP=SAMPLE_FILTER +MINORCOMP=SAMPLE_FILTER_DRV + +# The TARGETNAME variable is defined by the developer. It is the name of +# the target (component) that is being built by this makefile. It +# should NOT include any path or file extension information. + +TARGETNAME=sfilter + +# The TARGETPATH and TARGETTYPE variables are defined by the developer. +# The first specifies where the target is to be build. The second specifies +# the type of target. + +TARGETPATH=obj + +TARGETTYPE=DRIVER + +# The TARGETLIBS specifies additional libraries to link with you target +# image. Each library path specification should contain an asterisk (*) +# where the machine specific subdirectory name should go. + +#TARGETLIBS= + +# The INCLUDES variable specifies any include paths that are specific to +# this source directory. Separate multiple directory paths with single +# semicolons. Relative path specifications are okay. The INCLUDES +# variable is not required. Specifying an empty INCLUDES variable +# (i.e. INCLUDES= ) indicates no include paths are to be searched. +# +# NOTE: The "fsdk\inc" refers to the Microsoft supplied File Systems +# Developers Kit include directory. + +INCLUDES=..\inc;\ddk-40\inc;\fsdk\inc-40; + +# The SOURCES variable is defined by the developer. It is a list of all the +# source files for this component. Each source file should be on a separate +# line using the line continuation character. This will minimize merge +# conflicts if two developers adding source files to the same component. +# The SOURCES variable is required. If there are no platform common source +# files, an empty SOURCES variable should be used. (i.e. SOURCES= ) + +# Source files common to multiple platforms + +SOURCES=sfilinit.c \ + fastio.c \ + misc.c \ + attach.c \ + dispatch.c \ + sfilter.rc \ + create.c \ + fsctrl.c \ + close.c + +# i386 only source files (optional) +# assembler files MUST be in a subdirectory named i386 + +# i386_SOURCES=i386\source1.asm + +# mips only source files (optional) +# assembler files MUST be in a subdirectory named mips + +# MIPS_SOURCES=mips\source1.s + +# Next specify any additional options for the compiler. +# Define the appropriate CPU type (and insert defines +# in the appropriate header file) to get the right +# values for "uint8", "uint16" etc. typedefs. + +C_DEFINES= -DUNICODE -D_CPU_X86_ + +# required to compile for C++ + +# BLDCRT=1 + +# +# Next specify options for the linker. +# + +# DLLBASE=0x62900000 +# DLLENTRY=LibMain + +# specify which C runtimes to link with (default is libc.lib) + +# USE_CRTDLL=1 (link with crtdll.lib) +# USE_LIBCMT=1 (link with libcmt.lib) + +# Next specify one or more user mode test programs and their type +# UMTEST is used for optional test programs. UMAPPL is used for +# programs that always get built when the directory is built. + +# UMTYPE=nt +# UMTEST=foo*baz +# UMAPPL=foo*baz +# UMBASE=0x1000000 +# UMLIBS=obj\*\foo.lib + +# Defining either (or both) the variables NTTARGETFILE0 and/or NTTARGETFILES +# will cause MAKEFILE.DEF to include .\makefile.inc immediately after it +# specifies the top level targets (all, clean and loc) and their dependencies. +# MAKEFILE.DEF also expands NTTARGETFILE0 as the first dependent for the +# "all" target and NTTARGETFILES as the last dependent for the "all" target. +# Useful for specifying additional targets and dependencies that don't fit the +# general case covered by MAKEFILE.DEF + +# NTTARGETFILE0 is built before all other compiles specified in the sources +# file. + +# NTTARGETFILE1 is built after all other compiles specified in the sources +# file but before the link step. + +# NTTARGETFILES is built after all compiles/links specified by the +# sources file. + +# NTTARGETFILE0= +# NTTARGETFILE1= +# NTTARGETFILES= + +# Profiling for the working set tuner can be enabled by specifying the +# NTPROFILEINPUT variable. Examine the VGA display driver and perf samples +# for more details + +# NTPROFILEINPUT=YES + + +# The type of product being built - NT = kernel mode + +UMTYPE=nt + +``` + +`Windows Driver Development/Windows NT File System Internals/fsrec/WS_FTP.LOG`: + +```LOG +97.08.05 15:12 B C:\Users\Erik\NTFdisk\fsrec\readme3.txt --> geode /home/eray readme3.txt +97.08.06 14:20 B C:\Users\erik\NTFdisk\fsrec\readme3.txt <-- geode /work/nt/file.internals/disk readme3.txt + +``` + +`Windows Driver Development/Windows NT File System Internals/fsrec/inc/sfsrec.h`: + +```h +/************************************************************************* +* +* File: sfsrec.h +* +* Module: Sample File System Driver (Kernel mode execution only) +* +* Description: +* The main include file for the sample file system driver recognizer. +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +#ifndef _SFSREC_SFSREC_H_ +#define _SFSREC_SFSREC_H_ + +// Common include files; should be in the include dir of the MS supplied IFS Kit. +#include + +typedef struct SFsRecDeviceExtension { + BOOLEAN DidLoadFail; +} SFsRecDeviceExtension, *PtrSFsRecDeviceExtension; + +// try-finally simulation +#define try_return(S) { S; goto try_exit; } +#define try_return1(S) { S; goto try_exit1; } +#define try_return2(S) { S; goto try_exit2; } + +// Prototypes +extern void SFsRecUnload( +PDRIVER_OBJECT PtrFsRecDriverObject); + +extern NTSTATUS SFsRecFsControl( +PDEVICE_OBJECT DeviceObject, +PIRP Irp); + +#endif // _SFSREC_SFSREC_H_ + + +``` + +`Windows Driver Development/Windows NT File System Internals/fsrec/readme3.txt`: + +```txt +Sample File System Driver +------------------------ + +The sample file system driver recognizer code provided here can be^M +used to design and implement your own file system driver recognizer product. + +!!!!!!!!!!!!!!!!!!!!!!!WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +The code provided here is for illustration purposes only. It is not +to be used as-is. Substantial modifications, enhancements and testing +is required before installing any portion of the code on any +machine. + +DO NOT EXPECT THIS CODE TO WORK. IT IS ONLY TO BE USED AS A GUIDE +IN CONJUNCTION WITH THE TEXT CONTAINED IN THE BOOK IN DESIGNING YOUR +OWN FSD RECOGNIZER. IT IS LACKING IN FUNCTIONALITY AND HAS NEVER BEEN +INSTALLED OR TESTED AS IS COMMONLY REQUIRED. + +TO COMPILE THE SOURCES PROVIDED HERE, YOU MUST HAVE A COPY OF THE +"ntifs.h" HEADER FILE PROVIDED BY MICROSOFT AS PART OF THE WINDOWS NT +IFS KIT. + +!!!!!!!!!!!!!!!!!!!!!!!WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +Files provided: +-------------- + +src\sfsrec.c - File system driver recognizer initialization functions +inc\sfsrec.h - The "main" include file; also contains useful macros +src\makefile - makefile! +src\sources - Contains list of files to be built; use by build.exe +README.TXT - What you are reading + + + +``` + +`Windows Driver Development/Windows NT File System Internals/fsrec/src/makefile`: + +``` +# +# DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source +# file to this component. This file merely indirects to the real make file +# that is shared by all the driver components of the Windows NT DDK +# + +!INCLUDE $(NTMAKEENV)\makefile.def + +``` + +`Windows Driver Development/Windows NT File System Internals/fsrec/src/sfsrec.c`: + +```c +/************************************************************************* +* +* File: sfsrec.c +* +* Module: Sample File System Driver (Kernel mode execution only) +* +* Description: +* This file contains the initialization code for the kernel mode +* Sample FSD recognizer module +* +* Author: R. Nagar +* +* (c) 1996-97 Rajeev Nagar, All Rights Reserved +* +*************************************************************************/ + +#include "sfsrec.h" + +#define SFSD_FS_NAME L"\\SampleFSDRecognizer" + +PDEVICE_OBJECT PtrFSRecDeviceObject = NULL; +unsigned int SFsRecDidLoadFail = 0; + +extern NTSTATUS ZwLoadDriver( +IN PUNICODE_STRING DriverName); + + +/************************************************************************* +* +* Function: DriverEntry() +* +* Description: +* This routine is the standard entry point for all kernel mode drivers. +* The routine is invoked at IRQL PASSIVE_LEVEL in the context of a +* system worker thread. +* All FSD specific data structures etc. are initialized here. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: STATUS_SUCCESS/Error (will cause driver to be unloaded). +* +*************************************************************************/ +NTSTATUS DriverEntry( +PDRIVER_OBJECT DriverObject, +PUNICODE_STRING RegistryPath) +{ + NTSTATUS RC = STATUS_SUCCESS; + UNICODE_STRING DriverDeviceName; + + UNICODE_STRING FileSystemName; + OBJECT_ATTRIBUTES ObjectAttributes; + HANDLE FileSystemHandle = NULL; + IO_STATUS_BLOCK IoStatus; + PtrSFsRecDeviceExtension PtrExtension = NULL; + + try { + try { + + // Initialize the IRP major function table + DriverObject->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL] = SFsRecFsControl; + DriverObject->DriverUnload = SFsRecUnload; + + // Before creating a device object, check whether the FSD has been + // already loaded. Obviously you should know the name of the FSD that + // this recognizer has been created for. + RtlInitUnicodeString(&FileSystemName, L"\\SampleFSD"); + InitializeObjectAttributes(&ObjectAttributes, &FileSystemName, OBJ_CASE_INSENSITIVE, NULL, NULL); + // Try to open the file system now. + RC = ZwCreateFile(&FileSystemHandle, SYNCHRONIZE, &ObjectAttributes, &IoStatus, NULL, 0, + FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN, 0, NULL, 0); + if (RC != STATUS_OBJECT_NAME_NOT_FOUND) { + // The FSD must have been already loaded. + if (NT_SUCCESS(RC)) { + ZwClose(FileSystemHandle); + } + RC = STATUS_IMAGE_ALREADY_LOADED; + try_return(RC); + } + + // Create a device object representing the file system recognizer. + // Mount requests are sent to this device object. + RtlInitUnicodeString(&DriverDeviceName, L"\\SampleFSDRecognizer"); + + if (!NT_SUCCESS(RC = IoCreateDevice(DriverObject, // Driver object for the file system rec. + sizeof(SFsRecDeviceExtension), // Did a load fail? + &DriverDeviceName, // Name used above + FILE_DEVICE_DISK_FILE_SYSTEM, + 0, // No special characteristics + FALSE, + &(PtrFSRecDeviceObject)))) { + try_return(RC); + } + + PtrExtension = (PtrSFsRecDeviceExtension)(PtrFSRecDeviceObject->DeviceExtension); + + PtrExtension->DidLoadFail = FALSE; + + // Register the device object with the I/O Manager. + IoRegisterFileSystem(PtrFSRecDeviceObject); + + } except (EXCEPTION_EXECUTE_HANDLER) { + // we encountered an exception somewhere, eat it up + RC = GetExceptionCode(); + } + + try_exit: NOTHING; + } finally { + // start unwinding if we were unsuccessful + if (!NT_SUCCESS(RC) && PtrFSRecDeviceObject) { + IoDeleteDevice(PtrFSRecDeviceObject); + PtrFSRecDeviceObject = NULL; + } + } + + return(RC); +} + + + +/************************************************************************* +* +* Function: SFsRecUnload() +* +* Description: +* Unload the FS recognizer. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: None +* +*************************************************************************/ +void SFsRecUnload( +PDRIVER_OBJECT PtrFsRecDriverObject) +{ + // Simple. Unregister the device object, and delete it. + if (PtrFSRecDeviceObject) { + IoUnregisterFileSystem(PtrFSRecDeviceObject); + IoDeleteDevice(PtrFSRecDeviceObject); + + PtrFSRecDeviceObject = NULL; + } + + return; +} + + +/************************************************************************* +* +* Function: SFsRecFsControl() +* +* Description: +* The single dispatch routine provided. +* +* Expected Interrupt Level (for execution) : +* +* IRQL_PASSIVE_LEVEL +* +* Return Value: None +* +*************************************************************************/ +NTSTATUS SFsRecFsControl( +PDEVICE_OBJECT DeviceObject, +PIRP Irp) +{ + NTSTATUS RC = STATUS_UNRECOGNIZED_VOLUME; + PIO_STACK_LOCATION PtrIoStackLocation = NULL; + PtrSFsRecDeviceExtension PtrExtension = NULL; + PDEVICE_OBJECT PtrTargetDeviceObject = NULL; + UNICODE_STRING DriverName; + + FsRtlEnterFileSystem(); + + try { + + try { + + PtrIoStackLocation = IoGetCurrentIrpStackLocation(Irp); + ASSERT(PtrIoStackLocation); + + // Get a pointer to the device object extension. + PtrExtension = (PtrSFsRecDeviceExtension)(PtrFSRecDeviceObject->DeviceExtension); + + switch (PtrIoStackLocation->MinorFunction) { + case IRP_MN_MOUNT_VOLUME: + // Fail the request immediately if a previous load has failed. + // You are not required to do this however in your driver. + if (PtrExtension->DidLoadFail) { + try_return(RC); + } + + // Get a pointer to the target physical/virtual device object. + PtrTargetDeviceObject = PtrIoStackLocation->Parameters.MountVolume.DeviceObject; + + // The operations that you perform here are highly FSD specific. + // Typically, you would invoke an internal function that would + // (a) Get the disk geometry by issuing an IOCTL + // (b) Read the first few sectors (or appropriate sectors) + // to verify the on-disk metadata information. + // To get the drive geometry, use the documented I/O Manager + // routine called IoBuildDeviceIoControlRequest() to create an + // IRP. Supply an event with this request that you will wait on + // in case the lower-level driver returns STATUS_PENDING. + // Similarly, to actually read on-disk sectors, create an IRP + // using the IoBuildSynchronousFsdRequest() function call with + // a major function of IRP_MJ_READ. + + // After you have obtained on-disk information, verify the metadata. + // RC = SFsRecGetDiskInfoAndVerify(PtrTargetDeviceObject); + + if (NT_SUCCESS(RC)) { + // Everything looks good. Prepare to load the driver. + try_return(RC = STATUS_FS_DRIVER_REQUIRED); + } + break; + case IRP_MN_LOAD_FILE_SYSTEM: + // OK. So we processed a mount request and returned + // STATUS_FS_DRIVER_REQUIRED to the I/O Manager. + // This is the result! + RtlInitUnicodeString(&DriverName, L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\SFsd"); + RC = ZwLoadDriver(&DriverName); + if ((!NT_SUCCESS(RC)) && (RC != STATUS_IMAGE_ALREADY_LOADED)) { + PtrExtension->DidLoadFail = TRUE; + } else { + // Load succeeded. Mission accomplished! + IoUnregisterFileSystem(PtrFSRecDeviceObject); + } + break; + default: + RC = STATUS_INVALID_DEVICE_REQUEST; + break; + } + + } except (EXCEPTION_EXECUTE_HANDLER) { + RC = GetExceptionCode(); + } + + try_exit: NOTHING; + + } finally { + // Complete the IRP. + Irp->IoStatus.Status = RC; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + } + + FsRtlExitFileSystem(); + + return(RC); +} + + +``` + +`Windows Driver Development/Windows NT File System Internals/fsrec/src/sources`: + +``` +# - Execute the "build" command to make the sample FSD driver recognizer + +# +# The TARGETNAME variable is defined by the developer. It is the name of +# the target (component) that is being built by this makefile. It +# should NOT include any path or file extension information. +# + +TARGETNAME=sfsrec + +TARGETPATH=obj + +TARGETTYPE=DRIVER + + +INCLUDES=..\inc;\ddk-40\inc;\fsdk-40\inc; + +# Source files common to multiple platforms + +SOURCES=sfsrec.c + +# +# Next specify any additional options for the compiler. +# +C_DEFINES= -DUNICODE -D_CPU_X86_ + +# The type of product being built - NT = kernel mode + +UMTYPE=nt + +``` + +`Windows Driver Development/Windows Security/Anti Debug/回溯调用栈.md`: + +```md +按照自己思考的防止别人调用函数的思路,其实就是检查调用源,那么肯定是从调用栈入手: + +1. 在函数内部回溯调用堆栈,检查返回地址 +2. 返回地址为微信模块则正常调用,否则拒绝执行 +3. 可能检查一层,或者多层 +4. 可能检测返回地址在模块范围,或者是准确的返回地址 +5. vm相关逻辑,增加分析难度 + + +大概实现代码: + +```C++ +void TestAntiCall(DWORD a1) +{ +//vmstart + DWORD retAddr = *((DWORD*)((char*)&a1 - 4));//函数参数push到栈上之后,会把下一个要执行的指令地址压入栈中 + if(retAddr > wxModuleBase && retAddr < wxModuleEnd) { + //do things + } else { + //anti + //do nothing + } +//vmend +} +``` + +``` + +`Windows Driver Development/Windows Security/GDT.md`: + +```md +# GDT + +全称:全局段描述表(Global Descriptor Table) + +即GDT及其与段寄存器之间的关系。 + +CPU中的系统寄存器`GDTR`指向内存中的`GDT`,这是一个结构**数组**,数组中的每一个表项都是一个64位的`KGDTENTRY`数据结构,里面描述了段的起点(线性地址)和大小、保护模式、访问权取等信息。合在一起就是一个64位的“段描述块”。数组的大小为`NUM_GDT`,定义为28. + + +``` + +`Windows Driver Development/Windows Security/Hook/IDT HOOK.md`: + +```md +## 什么是IDT + +IDT (Interrupt Descriptor Table) 中断描述表 + +IDT表中存储了ISR(Interrupt Service Routines) 中断服务程序,这些例程会在中断产生时被调用。 + +IDT是一个有256个入口的线性表,每个IDT的入口是个8字节的描述符,所以整个IDT表的大小为`256 * 8 = 2048 bytes`,**每个中断向量关联了一个中断处理过程。所谓的中断向量就是把每个中断或者异常用一个0-255的数字识别。Intel称这个数字为向量。** + +对于IDT,操作系统使用IDTR寄存器来记录IDT的位置和大小。 + +`IDTR`寄存器是`48`位寄存器,用于保存IDT的信息。其中`低16位`代表IDT的大小,如下为`0x7ff`,`高32位`代表IDT的基地址,如下为`0x80b95400`。 + +**可以使用指令`sidt`读出IDTR寄存器中的信息,从而找到IDT在内存中的位置。** + +```C++ + typedef struct + { + WORD IDTLimit; + WORD LowIDTbase; //IDT的低半地址 + WORD HiIDTbase; //IDT的高半地址 + } IDTINFO; +``` + +```C++ + IDTINFO idt_info; + __asm{ + + sidt idt_info; //获取IDTINFO + + } +``` + + +首先看一下`IDTR`和`IDTL`寄存器的值 + +```cmd +kd> r idtr +idtr=80b95400 +kd> r idtr, idtl +idtr=80b95400 idtl=000007ff +``` + +## IDT HOOK + +IDT,中断就是停下现在的活动,去完成新的任务。一个中断可以源于软件或硬件。比如出现页错误,调用IDT中的0x0E。或用户请求系统服务(SSDT)时,调用IDT中的0x2E。而系统服务的调用是经常的,这个中断就能触发。我们现在就想办法,先在系统中找到IDT,然后确定0x2E在IDT中的地址,最后用我们的函数地址去取代它,这样一来,用户的进程一调用SSDT,我们的HOOK函数即被激发。 + +```C++ +#pragma pack(1) +typedef struct +{ + WORD LowOffset; //入口的低半地址 + WORD selector; + BYTE unused_lo; + unsigned char unused_hi:5; // stored TYPE ? + unsigned char DPL:2; + unsigned char P:1; // vector is present + WORD HiOffset; //入口地址的低半地址 +} IDTENTRY; +#pragma pack() +``` + +```C++ + int HookInterrupts() + { + + IDTINFO idt_info; //SIDT将返回的结构 + + IDTENTRY* idt_entries; //IDT的所有入口 + + IDTENTRY* int2e_entry; //我们目标的入口 + + __asm{ + + sidt idt_info; //获取IDTINFO + + } + //获取所有的入口 + idt_entries = + + (IDTENTRY*)MAKELONG(idt_info.LowIDTbase,idt_info.HiIDTbase); + + //保存真实的2e地址 + KiRealSystemServiceISR_Ptr = + MAKELONG(idt_entries[NT_SYSTEM_SERVICE_INT].LowOffset, + + idt_entries[NT_SYSTEM_SERVICE_INT].HiOffset); + + //获取0x2E的入口地址 + int2e_entry = &(idt_entries[NT_SYSTEM_SERVICE_INT]); + + __asm{ + + cli; // 屏蔽中断,防止被打扰 + + lea eax,MyKiSystemService; // 获得我们hook函数的地址,保存在eax + + mov ebx, int2e_entry; // 0x2E在IDT中的地址 + + mov [ebx],ax; // 把我们hook函数的低地址写入低地址 + + shr eax,16 //eax右移16,得到高地址 + + mov [ebx+6],ax; // 写入高地址 + + sti; //开中断 + + } + + return 0; + + } +``` + +## 键盘中断 + +8086PC机当中,键盘的输入将会引发9号中断,BIOS提供了int 9的中断例程。CPU在9号中断发生之后,会去执行`int 9`中断例程,然后从`60h端口`当中读取出扫描码,并且将其转换为相应的ASCII码和状态信息,存储在内存的指定的空间(键盘缓冲区或状态字节)当中。 + +> 一般的键盘输入,在CPU执行完int 9 中断例程之后都放到了键盘缓冲区当中,键盘缓冲区有`16个字单元`,可以存储15个按键扫描码和对应的ASCII码,这里之所以只能放15个是因为键盘缓冲区是用环形队列结构管理的内存区域,虽然缓冲区的本身长度为16个字,但出于判断“对列满”的考虑,它最多只能保存15个键盘信息。 + +BIOS提供了`int 16h`中断例程供程序员调用。这个例程中包含的一个最重要的功能是从键盘缓冲区中读取一个键盘输入。 + +```asm +mov ah,0 +int 16h +``` + +结果:ah(扫描码),al(ascii码) + +``` + +`Windows Driver Development/Windows Security/Hook/SSDT Hook.md`: + +```md +对应用程序而言,操作系统内核的作用体现在提供一组可以供其调用的函数,算是系统内核为应用软件提供的服务,所以称为`系统服务(system service)`或`系统调用(system call)` + + +内核被动和主动: +- 被动:从应用程序看,进程是主动和活跃的,内核是被动的,只是应进程的要求而提供服务。 +- 从整个系统看的话,内核是有活性的一面,诸多应用进程就是在内核的调度下运行的。(`进程调度`) + + +`系统服务`是在系统空间实现,软件是在用户空间实现。二者之间有着空间的间隔,实质上是CPU运行模式的不同。所以,在应用软件与内核之间必定存在着一个明确定义的`系统调用界面` + + +一般而言只有三种手段或原因可以使运行于用户空间的CPU转入系统空间: +- 中断(Interrupt):在开启了中断机制的情况下,只要有外部设备的中断请求到来,CPU就会自动转入系统空间。**中断只发生在两条指令之间,因而不会使正在执行的指令半途而废。对于CPU而言,因中断而进入系统空间是被动的,并且在CPU完全无法预知何时发生,所以中断的发生是异步的。 +- 异常(Exception):不管是在用户空间或系统空间,执行指令失败都会引起一次`异常`,CPU也会因此转入系统空间。同样,对于CPU而言,因异常而进入系统空间也是被动的、无法预知的。但是,这只是一般而言,实践中也可以通过故意引起异常而进入内核。 +- 自陷(Trap):为了让CPU能够主动的进入系统空间,绝大多数CPU都设有专门的`自陷`指令,系统调用通常就是靠自陷指令实现的。一执行这样的指令,CPU就转入了系统空间并从某个预定的地址开始执行指令,就像掉进了陷阱一样。**自陷指令在形式上与中断相似,就像是一次由CPU主动发出的中断请求。**这看起来更像是一次子程序调用。 + +Inter x86系列CPU还有一种称为`调用门`的机制,这此机制可以认为只是对自陷机制的改进,增加了一对指令`sysenter`和`sysexit`,用来实现一种称为`快速系统调用`的机制,并为此而增加了三个寄存器。 + +传统调用是通过自陷指令`int 0x2e`进入内核的,后来使用`快速系统调用` + +用户空间到系统空间的转换过程: +![exg](./images/1567581572(1).jpg) + +``` +ReadFile ====> 中介函数NtReadFile ==========> NtReadFile +``` + +使用快速系统调用之后的代码: +![pic1](./images/1567582661(1).jpg) + +代码中的`KUSER_SHARED_SYSCALL`是用户空间的一个地址,实际上是`0x7FFE0300`,这里存储着一个函数指针,指向`KiIntSystemCall`或`KiFastSystemCall`两个函数之一。系统在初始化的时候判断是否支持快速系统调用。 +![pic2](./images/1567582867(1).jpg) +![pic3](./images/1567582931(1).jpg) + + +这两个函数都在`ntdll.dll`中,这个dll在系统的初始化阶段就会被装入,内核判断处理器是否支持快速系统调用,从而在dll中找到函数的地址,将其写入用户空间地址`0x7FFE0300`,其实CPU写入的是系统空间的地址`0xFFDF0300`,但是系统空间`0xffdf0000`的地方与用户空间`0x7ffe0000`的地方就好像是一条隧道的两端,两块64KB的虚拟内存区间都映射到同一块物理内存。 + + + +## 系统调用的内核入口 KiSystemService + +当CPU执行`int 0x2e`时,CPU就进入了内核中的这个函数。 + +当用户空间通过自陷指令进入内核时,CPU自动将下列信息压入系统空间堆栈: +1. 用户空间的堆栈位置,包括堆栈段寄存器SS和堆栈指针ESP的内容 +2. CPU中“标志寄存器”EFLAGS的内容 +3. 用户空间的指令位置,包括代码段寄存器CS和指令指针EIP的内容。 + +进入系统空间时,系统空间堆栈上的内容如下: +![pic3](./images/1567585530(1).jpg) + + +那么系统空间堆栈又在哪里呢?每个线程都有自己的系统空间堆栈,其堆栈段寄存器SS和堆栈指针ESP的内容保存在一个`任务状态段`即`TSS`的数据结构里面。与此相应,CPU中有个称为`任务寄存器`即`TR`的段寄存器。每当从用户空间进入系统空间时,CPU就自动根据TR的指引从TSS中获取当前进程的SS和ESP两个寄存器的值。 + + +线程控制块`KTHREAD`结构中有个指针`ServiceTable`,指向本线程的系统调用表。这意味着不同的线程可以有不同的系统调用表。不过,实际使用中每个线程的ServiceTable不是指向`KeServiceDescriptorTable`就是指向`KeServiceDescriptorTableShadow`,前者仅用于基本系统调用,后者既可用于基本系统调用也可用于win32扩充系统调用。 + +``` + +`Windows Driver Development/Windows Security/Hook/i8042prt驱动.md`: + +```md +## I8042键盘 + +对于驱动来说,和键盘相关的最重要的硬件是两个芯片。**一个是`Intel 8042`芯片,位于主板,CPU通过IO端口直接和这个芯片通信,获得按键的扫描码或者发送各种键盘命令。**另一个是`Intel 8048`或者其他美容芯片,位于键盘中,这个芯片主要作用是从键盘的硬件中得到按键的扫描码,与i8042通信,控制键盘本身。 + +``` + +`Windows Driver Development/Windows Security/Hook/内核重载.md`: + +```md +以下是一般重载内核的步骤: + +1. 将内核文件加载到内存。 +2. 进行基址重定位。(代码段用新内核基址定位,数据段用老内核基址定位) + 重定位中包含了代码重定位和变量重定位,既然我们做的是重载内核,那么肯定是需要让原本走NT模块的流程转移到我们的新模块上,那么可以肯定的是代码重定位一定要在新模块上,至于变量,我个人的做法是指向原模块,因为即使是重载内核,也不能保证所有执行单元都会走新模块,这样保险一些,也简单一些,不过需要注意的是,变量重定位也包含IAT,所以我这里把IAT也指向新模块,否则修复输入表就没意义了,也可以防范IAT HOOK。 +3. 重定位ssdt结构。 +4. Hook KiFastCallEntry,让RING3进程调用走新内核。 +``` + +`Windows Driver Development/Windows Security/IDT.md`: + +```md +## KIDTENTRY 结构 + +``` +typedef struct _KIDTENTRY +{ + USHORT Offset; + USHORT Selector; + USHORT Access; + USHORT ExtendedOffset; +}KIDTENTRY,*PKIDTENTRY; +``` + +INTEL开发手册中描述: + +![idt](./images/1573628447(1).jpg) + +具体可见开发手册 Vol 3A 6-11 +``` + +`Windows Driver Development/Windows Security/Inject/APC注入.md`: + +```md +## APC注入 + +在Windows系统中,每个线程都会维护一个线程APC队列,通过 `QueueUserAPC` 把一个APC函数添加到指定线程的APC队列。每个线程都有它自己的APC队列,这个APC队列纪录了要求线程去执行的一些APC函数。 + +当线程alterable状态时,会执行APC函数。 + +一个线程内部使用SignalObjectAndWait 、SleepEx、WaitForSingleObjectEx、WaitForMultipleObjectsEx或MsgWaitForMultipleObjectsEx等函数把自己挂起时就是进入alertable状态。 + +注:本文目前只记录了大概原理,详细操作待补。 +``` + +`Windows Driver Development/Windows Security/KeUserModeCallback.md`: + +```md +## KeUserModeCallback过程 + +nt!KeUserModeCallback -> nt!KiCallUserMode -> nt!KiServiceExit -> ntdll!KiUserCallbackDispatcher -> int2B -> nt!KiCallbackReturn -> nt!KeUserModeCallback + +这是一个从 ring0 -> ring3 -> ring0 的过程。 + + +在准备堆栈完毕后,借用`KiServiceExit`的力量回到了`ring3`,它的着陆点是`KiUserCallbackDispatcher`,然后`KiUserCallbackDispatcher`从`PEB`中取出`KernelCallbackTable`的基址,再以`ApiIndex`作为索引在这个表中查找对应的回调函数并调用,调用完之后再`int2B`触发`nt!KiCallbackReturn`两次进入内核,修正堆栈后跳回`KeUserModeCallback`,完成调用。 +``` + +`Windows Driver Development/Windows Security/KernelCallbackTable.md`: + +```md +## 怎样获取KernelCallbackTable + + +首先KernelCallbackTable是存放在`PEB`中,需要找一个普通的进程进行调试: + +![image-20200929162534322](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200929162534322.png) + +查看`PEB`及其结构体: + +![image-20200929162706499](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200929162706499.png) + + + + + +![image-20200929162900653](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200929162900653.png) + +查看`KernelCallbackTable`中的内容: + +![image-20200929162944726](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200929163010604.png) + +Windbg 命令 `dqs`: 会显示给定范围的内存内容。当该内存是符号表中的一系列地址时,相应的符号也会显示出来。 + +![image-20200929163010604](https://raw.githubusercontent.com/supermanc88/ImageSources/master/image-20200929162944726.png) +``` + +`Windows Driver Development/Windows Security/PE/输入表结构.md`: + +```md +## 输入表 + +在PE文件头的optional头中,数据目录表的第2个成员指向输入表。 + +输入表以一个`IMAGE_IMPORT_DESCRIPTOR`结构的数组开始,每个被PE文件隐式链接的DLL都有一个IID,在这个结构中,没有指定具体的数组项数,直到最后一个结构为空。 + +``` +typedef struct _IMAGE_IMPORT_DESCRIPTOR { + union { + DWORD Characteristics; // 0 for terminating null import descriptor + DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA) + } DUMMYUNIONNAME; + DWORD TimeDateStamp; // 0 if not bound, + // -1 if bound, and real date\time stamp + // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND) + // O.W. date/time stamp of DLL bound to (Old BIND) + + DWORD ForwarderChain; // -1 if no forwarders + DWORD Name; + DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses) +} IMAGE_IMPORT_DESCRIPTOR; +``` +- OriginalFirstThunk: 包含指向输入名称表(INT)的RVA。INT是一个`IMAGE_THUNK_DATA`结构的数组,数组中的每个`IMAGE_THUNK_DATA`结构都指向`IMAGE_IMPORT_BY_NAME`结构,数组以一个内容为0的`IMAGE_THUNK_DATA`结构结束。 +- FirstThunk: 包含指向输入地址表(IAT)的RVA。 + + +``` +typedef struct _IMAGE_THUNK_DATA32 { + union { + DWORD ForwarderString; // PBYTE + DWORD Function; // PDWORD + DWORD Ordinal; + DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME + } u1; +} IMAGE_THUNK_DATA32; +``` + +每个`IMAGE_THUNK_DATA`元素对应于一个从可执行文件输入的函数。 + + + +``` + +`Windows Driver Development/Windows Security/WRK内核编译调试环境搭建.md`: + +```md +## 工具 + +1. windows server 2003 sp2 (sp1也可以,或xp x64) +2. Vmware + + +## 编译WRK源码 + +将wrk-v1.2拷贝到虚拟机server 2003中,添加环境变量`Path`,内容为`path\tools\x86` + + +cmd命令cd到`path\base\ntos`目录下,执行命令`nmake -nologo x86=` + +注: + +1. 可能提示系统缺少`msvcr71.dll`和`msvcp71.dll`,从网上找个加到`system32`目录下即可。 +2. 源码路径中最好没有中文和空格 + +编译出: + +- wrkx86.exe + +## 选择Hal + +WRK提供了多个版本的hal,为了知道当前系统的hal类型: + +``` +link -dump -all \WINDOWS\system32\hal.dll | findstr pdb +``` + +![image](https://raw.githubusercontent.com/supermanc88/Document/master/Windows%20Security/images/1572507617(1).jpg) + +在WRK中,根据以下关系选择相应的HAL库: + +halacpi.dll -> halacpim.dll + +halaacpi.dll->halmacpi.dll + +halapic.dll->halmps.dll + +所以应将`halmacpi.dll`拷贝到`system32`目录下。 + + +## 环境搭建 + +1. 将`wrkx86.exe`复制到`system32`下 +2. 将对应的`hal.dll`复制到`system32`下 +3. 显示隐藏文件,在`boot.ini`中添加以下: + +``` +multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="WRK V1.2" /kernel=wrkx86.exe /hal=halmacpi.dll +multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="WRK V1.2[debug]" /kernel=wrkx86.exe /hal=halmacpi.dll /debug /debugport=com1 /baudrate=115200 +``` + +虚拟机进行如下设置: +![image](https://raw.githubusercontent.com/supermanc88/Document/master/Windows%20Security/images/1572509079(1).jpg) + +至此,调试环境搭建完成 + +``` + +`Windows Driver Development/Windows Security/Windows驱动未公开秘籍.md`: + +```md +# Windows驱动未公开秘籍 + +## Driver_Object + +`Driver_Object`结构体中有一个`DriverSection`成员。该成员为指向结构体`LDR_DATA_TABLE_ENTRY`的指针。 + +以下提供的结构体可能不准确,来自react os +``` +typedef struct _LDR_DATA_TABLE_ENTRY { + LIST_ENTRY LoadOrder; + LIST_ENTRY MemoryOrder; + LIST_ENTRY InitializationOrder; + PVOID ModuleBaseAddress; + PVOID EntryPoint; + ULONG ModuleSize; + UNICODE_STRING FullModuleName; + UNICODE_STRING ModuleName; + ULONG Flags; + USHORT LoadCount; + USHORT TlsIndex; + union { + LIST_ENTRY Hash; + struct { + PVOID SectionPointer; + ULONG CheckSum; + }; + }; + ULONG TimeStamp; +} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY; +``` + +这个结构体可以用来干什么? + +### 遍历模块 + `LoadOrder`是一个`LIST_ENTRY`链表,用来连接系统中的内核模块,一般情况下,`thisModule->LoadOrder.Flink`指向的就是`ntoskrnl`模块。 + +具体忘记了,也有人说`Ntoskrnl is always first entry in the list`。也就是需要一直向前遍历,直到找到`ntoskrnl` + +如果想找某个模块的基址,可遍历这个链表,通过`ModuleName`对比找出`ModulBaseAddress`。 + +### 驱动隐藏 + 也看到有人用这个去做驱动隐藏功能,实现思路是将当前的驱动模块从`LoadOrder`链表中进行断链。 +``` + +`Windows Driver Development/Windows Security/ntoskrnl.md`: + +```md +Re: 内核文件ntoskrnl.exe, ntkrnlpa.exe, ntkrnlmp.exe, ntkrpamp.exe到底有什么区别? + + +简单来说,是同一套源代码根据编译选项的不同而编译出四个可执行文件,分别用于: +ntoskrnl - 单处理器,不支持PAE +ntkrnlpa - 单处理器,支持PAE +ntkrnlmp - 多处理器,不支持PAE +ntkrpamp - 多处理器,支持PAE +在Vista之前,安装程序会在安装时根据系统的配置选择两个多处理器或者两个单处理器的版本复制到目标系统中。从Vista开始,会统一使用多处理器版本,因为多处理器版本运行在单处理器上只是效率稍微低一些。 + + + +```C +NTSTATUS Status; +PUCHAR BaseAddress = NULL; +NTSTATUS ntStatus; +PMODULES pModules; +ULONG NeededSize; +pModules = (PMODULES)&pModules; +ntStatus = NtQuerySystemInformation(SystemModuleInformation, pModules, 4, &NeededSize); +if(ntStatus == STATUS_INFO_LENGTH_MISMATCH) +{ + pModules = (PMODULES)ExAllocatePool(PagedPool, NeededSize); + if(!pModules) + return STATUS_INSUFFICIENT_RESOURCES; + ntStatus = NtQuerySystemInformation(SystemModuleInformation, pModules, NeededSize, NULL); + if(!NT_SUCCESS(ntStatus)) + { + ExFreePool(pModules); + return ntStatus; + } +} +if(!NT_SUCCESS(ntStatus)) +{ + return ntStatus; +} +BaseAddress = (PUCHAR)pModules->smi.Module[0].MappedBase; +``` +``` + +`Windows Driver Development/Windows Security/shadow SSDT获取.md`: + +```md +# Shadow SSDT获取 + +本文仅提供思路 + +## x86 系统 +### 硬编码获取 + +已知`KeServiceDescriptorTable`是导出的,个别系统可以通过此变量的地址加偏移找出`KeServiceDescriptorTableShadow`的地址。 + +例: +``` +// win7 32位下 +kd> dps nt!KeServiceDescriptorTable +83fbc9c0 83ed0d9c nt!KiServiceTable +83fbc9c4 00000000 +83fbc9c8 00000191 +83fbc9cc 83ed13e4 nt!KiArgumentTable +83fbc9d0 00000000 +83fbc9d4 00000000 +83fbc9d8 00000000 +83fbc9dc 00000000 +83fbc9e0 83f2f6af nt!KdpSwitchProcessor +83fbc9e4 00000000 +83fbc9e8 02f7d1be +83fbc9ec 000000bb +83fbc9f0 00000011 +83fbc9f4 00000100 +83fbc9f8 5385d2ba +83fbc9fc d717548f +83fbca00 83ed0d9c nt!KiServiceTable +83fbca04 00000000 +83fbca08 00000191 +83fbca0c 83ed13e4 nt!KiArgumentTable +83fbca10 926a6000 win32k!W32pServiceTable +83fbca14 00000000 +83fbca18 00000339 +83fbca1c 926a702c win32k!W32pArgumentTable +83fbca20 00000000 +83fbca24 00000000 +83fbca28 83fbca24 nt!KiNonNumaDistance +83fbca2c 00000340 +83fbca30 00000340 +83fbca34 857d5488 +83fbca38 00000007 +83fbca3c 00000000 + +``` + + +### 通过KeAddSystemServiceTable获取 + +这个方法更加通用 +``` +kd> u nt!KeAddSystemServiceTable L20 +nt!KeAddSystemServiceTable: +81588690 8bff mov edi,edi +81588692 55 push ebp +81588693 8bec mov ebp,esp +81588695 837d1801 cmp dword ptr [ebp+18h],1 +81588699 7750 ja nt!KeAddSystemServiceTable+0x5b (815886eb) +8158869b 8b4d18 mov ecx,dword ptr [ebp+18h] +8158869e c1e104 shl ecx,4 +815886a1 83b90024438100 cmp dword ptr nt!KeServiceDescriptorTable (81432400)[ecx],0 +815886a8 7541 jne nt!KeAddSystemServiceTable+0x5b (815886eb) +815886aa 83b9c023438100 cmp dword ptr nt!KeServiceDescriptorTableShadow (814323c0)[ecx],0 +815886b1 7538 jne nt!KeAddSystemServiceTable+0x5b (815886eb) +815886b3 837d1801 cmp dword ptr [ebp+18h],1 +815886b7 8b5508 mov edx,dword ptr [ebp+8] +815886ba 8b4514 mov eax,dword ptr [ebp+14h] +815886bd 56 push esi +815886be 8b750c mov esi,dword ptr [ebp+0Ch] +815886c1 57 push edi +815886c2 8b7d10 mov edi,dword ptr [ebp+10h] +815886c5 8991c0234381 mov dword ptr nt!KeServiceDescriptorTableShadow (814323c0)[ecx],edx +815886cb 89b1c4234381 mov dword ptr nt!KeServiceDescriptorTableShadow+0x4 (814323c4)[ecx],esi +815886d1 89b9c8234381 mov dword ptr nt!KeServiceDescriptorTableShadow+0x8 (814323c8)[ecx],edi +815886d7 8981cc234381 mov dword ptr nt!KeServiceDescriptorTableShadow+0xc (814323cc)[ecx],eax +815886dd 0f85cd240d00 jne nt! ?? ::NNGAKEGL::`string'+0x25e0a (8165abb0) +815886e3 5f pop edi +815886e4 b001 mov al,1 +815886e6 5e pop esi +815886e7 5d pop ebp +815886e8 c21400 ret 14h +815886eb 32c0 xor al,al +815886ed ebf8 jmp nt!KeAddSystemServiceTable+0x57 (815886e7) +815886ef cc int 3 +815886f0 cc int 3 + +``` + +在这一句 +``` +815886c5 8991c0234381 mov dword ptr nt!KeServiceDescriptorTableShadow (814323c0)[ecx] +``` +可以直接找到`KeServiceDescriptorTableShadow`的地址,通过特征码搜索即可。 + + +## x64 系统 + +在64位系统下最好还是老老实实的搜索特征码: + +### 硬编码 + +不是说硬编码彻底不能用: +``` +// win7 x64下还是可以找到 +kd> dqs nt!KeServiceDescriptorTable +fffff800`04103840 fffff800`03ed3300 nt!KiServiceTable +fffff800`04103848 00000000`00000000 +fffff800`04103850 00000000`00000191 +fffff800`04103858 fffff800`03ed3f8c nt!KiArgumentTable +fffff800`04103860 00000000`00000000 +fffff800`04103868 00000000`00000000 +fffff800`04103870 00000000`00000000 +fffff800`04103878 00000000`00000000 +fffff800`04103880 fffff800`03ed3300 nt!KiServiceTable +fffff800`04103888 00000000`00000000 +fffff800`04103890 00000000`00000191 +fffff800`04103898 fffff800`03ed3f8c nt!KiArgumentTable +fffff800`041038a0 fffff960`00171f00 win32k!W32pServiceTable +fffff800`041038a8 00000000`00000000 +fffff800`041038b0 00000000`0000033b +fffff800`041038b8 fffff960`00173c1c win32k!W32pArgumentTable +``` + +### 特征码搜索 + +x64下不使用`KeAddSystemServiceTable`函数搜索。使用`KiSystemCall64`进行搜索 + +`KiSystemCall64`的地址通过`__readmsr(0xc0000082)`获取 + +``` +kd> u nt!KiSystemCall64 L100 +nt!KiSystemCall64: +fffff800`03ed1640 0f01f8 swapgs +fffff800`03ed1643 654889242510000000 mov qword ptr gs:[10h],rsp +fffff800`03ed164c 65488b2425a8010000 mov rsp,qword ptr gs:[1A8h] +fffff800`03ed1655 6a2b push 2Bh +fffff800`03ed1657 65ff342510000000 push qword ptr g +... +fffff800`03ed1765 8bf8 mov edi,eax +fffff800`03ed1767 c1ef07 shr edi,7 +fffff800`03ed176a 83e720 and edi,20h +fffff800`03ed176d 25ff0f0000 and eax,0FFFh +nt!KiSystemServiceRepeat: +fffff800`03ed1772 4c8d15c7202300 lea r10,[nt!KeServiceDescriptorTable (fffff800`04103840)] +fffff800`03ed1779 4c8d1d00212300 lea r11,[nt!KeServiceDescriptorTableShadow (fffff800`04103880)] +fffff800`03ed1780 f7830001000080000000 test dword ptr [rbx+100h],80h +fffff800`03ed178a 4d0f45d3 cmovne r10,r11 +fffff800`03ed178e 423b441710 cmp eax,dword ptr [rdi+r10+10h] +fffff800`03ed1793 0f83e9020000 jae nt!KiSystemServiceExit+0x1a7 (fffff800`03ed1a82) +fffff800`03ed1799 4e8b1417 mov r10,qword ptr [rdi+r10] +fffff800`03ed179d 4d631c82 movsxd r11,dword ptr [r10+rax*4] +fffff800`03ed17a1 498bc3 mov rax,r11 +... +``` + + +在win10下,在`KiSystemCall64`函数中是找不到的,这时需要通过特征码先在此函数中找到`KiSystemServiceUser`函数地址 + +``` +fffff803`23733383 e9631ae9ff jmp nt!KiSystemServiceUser(fffff803`235c4deb) +fffff803`23733388 c3 ret +``` + +然后在`KiSystemServiceUser`函数中通过特征码搜索出`Shadow SSDT`的地址。 + +``` + +`Windows Driver Development/Windows Security/内核读写只读内存.md`: + +```md +# 内核读写只读内存方法 + +## Cr0 寄存器 + +![cr0](./images/1576661590(1).jpg) + +- CR0.WP + **Write Protect(bit 16 of CR0)** 当设置为1时,内存为只读,设置为0时,内存可写。 + +``` +//1 关闭写保护 +asm + push eax + mov eax, CR0 + and eax, 0FFFEFFFFh + mov CR0, eax + pop eax +end; + +//2 打开写保护 +asm + push eax + mov eax, CR0 + or eax, NOT 0FFFEFFFFh + mov CR0, eax + pop eax +end; +``` + +## 内存描述表(MDL) + +``` +typedef + _Struct_size_bytes_(_Inexpressible_(sizeof(struct _MDL) + // 747934 + (ByteOffset + ByteCount + PAGE_SIZE-1) / PAGE_SIZE * sizeof(PFN_NUMBER))) + struct _MDL { + struct _MDL *Next; + CSHORT Size; + CSHORT MdlFlags; + + struct _EPROCESS *Process; + PVOID MappedSystemVa; /* see creators for field size annotations. */ + PVOID StartVa; /* see creators for validity; could be address 0. */ + ULONG ByteCount; + ULONG ByteOffset; +} MDL, *PMDL; +``` + +代码: + +``` +PMDL mdl = IoAllocateMdl(virtualAddress, sizeof(PVOID), FALSE, FALSE, NULL); + +MmBuildMdlForNonPagedPool(mdl); + +mdl->MdlFlags |= MDL_MAPPED_TO_SYSTEM_VA; // 方式1 使内存可写 +// MmProbeAndLockPages(mdl, KernelMode, IoWriteAccess); 方式2 使内存可写 +PVOID MapAddress = MmMapLockedPagesSpecifyCache(mdl, KernelMode, MmCached, NULL, NULL, NormalPagePriority); + +// do something + +MmUnmapLockedPages(MapAddress, mdl); + +``` + +``` + +`Windows Driver Development/Windows Security/禁用内核调试.md`: + +```md +Windows 启动过程中会调用两次`KdInitSystem`函数 + +第一次调用`KdInitSystem`分别会初始化如下变量: + +1. `KdPitchDebugger`:Boolean 用来表示是否显示的抑制内核调试,当启动项中包含`/NODEBUG`选项时,这个变量会被置为`TURE` + +2. `KdDebuggerEnabled`:Boolean 用来表示内核调试是否被启用。当启动项中包含`/DEBUG`或者`/DEBUGPORT`并且不包含`/NODEBUG`时,这个变量置为`TURE` + +3. `KiDebugRoutine`: 函数指针PVOID 用来记录内核调试引擎的异常处理回调函数,当内核调试引擎活动时,指向`KdpTrap`函数,否则指向`KdpStub`函数 + +4. `KdpBreakpointTable`:结构体数组指针PVOID 用来记录代码断点。每一个元素为`BREAKPOINT_ENTRY`结构,用来描述一个断点,包括断点地址。 + +5. `KdDebuggerNotPresent`也是判断内核调试状态的标志(收到复位包之后,将`KdDebuggerNotPresent`设置为0) + +6. `KdEnteredDebugger`,在内核冻结时,会对`KdEnterDebugger`赋值,TP就是根据这个点进行MDL判断。 + +总结表格为: + +|函数|调试状态|非调试状态| +|--|--|--| +|KdPitchDebugger|FALSE|TRUE| +|KdDebuggerEnabled|TRUE|FALSE| +|KiDebugRoutine|KdpTrap|KdpStub| +|KdDebuggerNotPresent|0|1| +``` + +`Windows Driver Development/Windows Security/系统调用.md`: + +```md +## 应用层ZwCreateFile代码 + +```asm +mov r10,rcx +mov eax,55 +test byte ptr ds:[7FFE0308],1 +jne ntdll.7FFE9EB8F035 +syscall +ret +int 2E +ret +``` + +说明SSDT序号为0x55,使用`syscall`进入内核 + +### syscall: + +SYSCALL invokes an OS system-call handler at privilege level 0. It does so by loading RIP from the `IA32_LSTAR MSR` (after saving the address of the instruction following SYSCALL into RCX) + +SYSCALL also saves RFLAGS into R11 and then masks RFLAGS using the IA32_FMASK MSR **(MSR address C0000084H)**; specifically, the processor clears in RFLAGS every bit corresponding to a bit that is set in the IA32_FMASK MSR. + +SYSCALL loads the `CS` and `SS` selectors with values derived from `bits 47:32 of the IA32_STAR MSR`. + +The SYSCALL instruction does **not** save the stack pointer (RSP). If the OS system-call handler will change the stack pointer, it is the responsibility of software to save the previous value of the stack pointer. This might be done prior to executing SYSCALL, with software restoring the stack pointer with the instruction following SYSCALL (which will be executed after `SYSRET`). Alternatively, the OS system-call handler may save the stack pointer and restore it before executing SYSRET. + +**Instruction ordering**. Instructions following a SYSCALL may be fetched from memory before earlier instructions complete execution, but they will not execute (even speculatively) until all instructions prior to the SYSCALL have completed execution (the later instructions may execute before data stored by the earlier instructions have become globally visible). + +- IA32_STAR(0xC0000081):Ring 0和Ring 3段基址,以及SYSCALL的EIP。在较低的32位中存储的是SYSCALL的EIP,在第32-47位存储内核段基址,在第48-63为存储用户段基址。 +- IA32_CSTAR(0xC0000083):兼容模式下,SYSCALL的内核RIP相对寻址。 +- IA32_LSTAR(0xC0000082):长模式(Long Mode,即64位)下,SYSCALL的内核RIP相对寻址。 + + + +--- + + +对于应用程序而言,操作系统内核的作用体现在一组**可以供其调用的函数,称为“系统调用”**。 + +从程序运行的角度看,进程是主动的,是发出调用请求的一方,而内核是被动的,只是回应进程的要求而提供服务。但是,从整个系统的角度看,内核也有主动的一面,诸多应用程序就是在内核的调度下运行。所以内核有其两面性,离开进程调度,内核就只剩下被动的一面,那就类似于一个运行时程序库了。 + +系统调用就体现了内核被动的一面。 + +## 用户态转内核态 + +因运行状态和所执行的程序所在内存区间的不同,CPU即可运行于“用户层”,也可运行于“内核层”。CPU要从系统空间转入用户空间是容易的,因为运行于系统态的CPU可以通过一些(只允许在系统态使用的)特权指令改变其运行状态。但是,反过来从用户空间转入内核空间就不容易了,因为运行在用户空间的CPU是不允许执行特权指令的。所以,一般使用以下方式进入内核: + +- 中断(Interrupt):在开启中断机制的情况下,只要有外部设备的中断请求到来,CPU就会自动转入系统空间,并从系统空间中某个预定的地址开始执行指令。 + +- 异常(Exception):不管是在用户态还是在内核态,执行指令时的失败都会引起一次异常。CPU也会因此而转入系统空间(如果原来不在系统空间),并从某个预定的地址开始执行指令,从而可以在系统空间对发生的失败作出反应。 + +- 自陷(Trap):上面两种方式都是被动的进入系统空间,为了让CPU能够主动的进入系统空间,绝大多数的CPU都有专门的“自陷”指令,系统调用通常就是靠自陷指令实现的。 + +> 自陷指令在形式上也与中断相似,就像是一次由CPU主动发出的中断请求。对于CPU而言,这是碾工执行了一条指令的结果,所以是主动的,对于程序而言,则一条自陷指令的作用相当于一次子程序调用,只是这个子程序存在于系统空间。**传统的Windows系统调用正是通过自陷指令“int 0x2e”进入内核实现系统调用的**。 + + + +### 过程 + +1. 当Cpu在用户空间执行自陷指令“int 0x2e”时,CPU的运行状态从用户态切换到系统态; +2. 并从“任务状态段”TSS中装入本线程的系统空间堆栈段寄存器SS和堆栈指针ESP; +3. 再依次把用户空间的堆栈段寄存器SS、堆栈指针ESP、标志位寄存器EFLAGS、代码段寄存器CS、指令计数器EIP的内容压入用户系统空间堆栈 +4. 然后从“中断向量表”IDT中以0x2e为“中断向量”,即数组下标获取程序入口,开始执行内核中的程序。 +5. 从系统调用返回则通过“中断返回”指令“iret”实现上述过程的逆过程。 + + +## 快速调用机制 + +快速系统调用通过`sysenter`实现,在x86系统上几乎不用。为了实现快速调用,CPU中增加了三个`MSR`寄存器: + +- SYSENTER_CS_MSR + +- SYSENTER_EIP_MSR + +- SYSENTER_ESP_MSR + +执行`sysenter`指令时,CPU根据这三个寄存器的内容设置`跳转目标`和`堆栈指针`。这样,只要预先妥善设置这三个MSR寄存器,就可以使得执行指令`sysenter`以后: + +- CPU的CS就指向系统空间, + +- EIP就指向内核中快速系统调用的入口 + +- 而SS和ESP的组合则指向系统空间堆栈的顶部。 + +MSR是`Mode Specific Register`的缩写,对于MSR寄存器只能通过特权指令`rdmsr`和`wrmsr`读写。 + + + +## 系统调用的内核入口 + +本节以reactos为例,可以看到中断向量0x2e的处理函数是`KiSystemService`,所以,当CPU执行“int 0x2e`指令时,CPU就进入了内核中的这个函数。 + +详细可见[过程](#procdure) + +进入系统空间时,系统空间堆栈上的内容如下: + +![stackcontent](./images/1573630080(1).jpg) + +### 快速系统调用 + +在采用快速系统调用时,用户空间的“int 0x2e”指令大体上被一条`sysenter`指令所替代,这个指令不涉及堆栈操作,依赖MSR寄存器。 + +当执行sysenter时,CPU进入系统态,并且: + +- 把寄存器`SYSENTER_CS_MSR`的内容复制到CS中 + +- 把寄存器`SYSENTER_EIP_MSR`的内容复制到EIP中 + +- 把寄存器`SYSENTER_CS_MSR`的内容+8写入SS + +- 把寄存器`SYSENTER_ESP_MSR`的内容复制到ESP + +但是此时CPU不像在执行自陷指令时那样自动把用户空间的堆栈指针保存在系统空间堆栈止,甚至也不将返回地址压入堆栈,所以需要相应的程序来完成这些操作。 + +`KiFastCallEntry`函数是系统空间中快速系统调用的总入口。当从内核态返回到用户态时,系统会调用`KiFastSystemCallRet`函数,它会去找一个叫做`SharedUserData`的数据结构,这个结构中保存了目标地址。 + + +### 从内核中发起系统调用 + +上面描述了从用户空间进行系统调用的全过程以及两种方法。 + +系统调用一般是从用户空间发起的,但是Windows也允许从内核进行系统调用。 + +一般而言,在内核里面不能直接调用`NtReadFile`一类的函数,这一方面是因为Windows内核并不导出这些函数,另一方面也是因为这些函数往往要求在系统空间堆栈上有个属于本次调用的自陷框架,而从内核中直接调用这些函数时显然没有这么一个框架。 + +但是,既然CPU已经运行于内核中,系统空间堆栈上就已经有了这样的框架,那可能是自陷框架、中断框架或异常框架,但都不是本次调用而形成的。所以内核中一般不能直接调用`NtReadFile`一类的函数。这就是为什么在内核中只能通过`ZwReadFile`一类函数进行系统调用的原因。 + +> 在老版本的reactos中,ZwReadFile算是一个中介函数,其内部是调用的`KiSystemService`用来构建系统调用框架,而在最新的代码中,NtReadFile源码中,则直接实现了文件的读写,不再经过系统调用。 + + + + + +### 系统空间堆栈 + +每个线程都有自己的系统空间堆栈,其堆栈段寄存器SS和堆栈指针ESP的内容保存在一个称为**任务状态段**即TSS的数据结构里面。与此相应,CPU中有个称为`任务寄存器即TR寄存器`。从TR寄存器中拿到SS和ESP。 + +与普通的寄存器不同,TR寄存器有两条专用的特权指令,`ltr`和`str`。 + +自陷指令所蕴含的操作序列其实是很长的,它要根据TR的指引从TR寄存器中当前线程的系统空间堆栈,再把好几个寄存器的内容压入堆栈,还要根据IDRT的指引从中断向量表中获取CS和EIP的值,这里面所涉及的时钟周期显然不少,正因为这样,后来才有了“快速系统调用”指令`sysenter`和`sysexit`的出现。 + + +**Windows内核有个特殊的基本要求,就是只要CPU在内核中运行,就得使段寄存器FS指向一个称为KPCR的数据结构。单CPU的系统只有一个KPCR,而多CPU的系统中则每个CPU都有自己的KPCR结构。** + +当段寄存器指向这个数据结构之后,还要使寄存器ESI指向当前线程,即当前线程的`线程控制块`数据结构的起点。 + + +`KiIntSystemCall`和`KiFastSystemCall`这两个函数都在用户空间的动态链接库`ntdll.dll`中,而`ntdll.dll`是个基本的“系统dll”,实际上在系统的初始化阶段就会被装入,要不然什么进程也运行不了。从那以后,这个Dll就一直驻留在内存中;不管在什么进程中,其出现的位置都是相同的。内核在初始化阶段根据处理器的`CPUID`判断其是否支持快速系统调用,而从`ntdll.dll`的映像中找到`KiFastSystemCall`或`KiIntSystemCall`的地址,将其写入用户空间的地址`0x7ffe0330(不变)`。其实CPU写入的是系统空间的地址`0xffdf0300(不变)`,**但是系统空间`0xffdf0000`的地方与用户空间`0x7ffe0000`的地方就好像是一条隧道的两端,两块64KB的虚存区间都映射到同一块物理内存。** + +``` + +`Windows Driver Development/Windows Security/系统调用(二).md`: + +```md +本文章用一个例子(ReadFile)来深入理解系统调用 + + +## ReadFile + +在reactos中,ReadFile的实现为: + +``` +BOOL WINAPI +ReadFile(IN HANDLE hFile, + IN LPVOID lpBuffer, + IN DWORD nNumberOfBytesToRead, + OUT LPDWORD lpNumberOfBytesRead OPTIONAL, + IN LPOVERLAPPED lpOverlapped OPTIONAL) +{ + NTSTATUS Status; + +// 此处省略部分 +... + if (lpOverlapped != NULL) + { + LARGE_INTEGER Offset; + PVOID ApcContext; + + Offset.u.LowPart = lpOverlapped->Offset; + Offset.u.HighPart = lpOverlapped->OffsetHigh; + lpOverlapped->Internal = STATUS_PENDING; + ApcContext = (((ULONG_PTR)lpOverlapped->hEvent & 0x1) ? NULL : lpOverlapped); + + Status = NtReadFile(hFile, + lpOverlapped->hEvent, + NULL, + ApcContext, + (PIO_STATUS_BLOCK)lpOverlapped, + lpBuffer, + nNumberOfBytesToRead, + &Offset, + NULL); + + /* return FALSE in case of failure and pending operations! */ + if (!NT_SUCCESS(Status) || Status == STATUS_PENDING) + { + if (Status == STATUS_END_OF_FILE && lpNumberOfBytesRead != NULL) + *lpNumberOfBytesRead = 0; + + BaseSetLastNTError(Status); + return FALSE; + } + + if (lpNumberOfBytesRead != NULL) + *lpNumberOfBytesRead = lpOverlapped->InternalHigh; + } + else + { + IO_STATUS_BLOCK Iosb; + + Status = NtReadFile(hFile, + NULL, + NULL, + NULL, + &Iosb, + lpBuffer, + nNumberOfBytesToRead, + NULL, + NULL); + + /* Wait in case operation is pending */ + if (Status == STATUS_PENDING) + { + Status = NtWaitForSingleObject(hFile, FALSE, NULL); + if (NT_SUCCESS(Status)) Status = Iosb.Status; + } + +// 此处省略部分 +... + + TRACE("ReadFile() succeeded\n"); + return TRUE; +} +``` + + +本代码具体的细节不讨论,可以看到`ReadFile`最终调用的是`NtReadFile`函数 + +如图: +![ntreadfile](./images/1573556002(1).png) + +在`ntdll.dll`动态库中导出了`NtReadFile`函数 + +那么`NtReadFile`函数是怎么实现的呢? + + +## ntdll 中的 NtReadFile + +函数`NtReadFile`函数在ntdll.dll中的实现: + +``` +// 此为在IDA中的代码,实际上这段代码就是汇编写的,就是如下样子: +.text:000000018009F810 NtReadFile proc near ; CODE XREF: EtwpFinalizeLogFileHeader+112↑p +.text:000000018009F810 ; EtwpAddLogHeaderToLogFile+55161↓p ... +.text:000000018009F810 mov r10, rcx ; NtReadFile +.text:000000018009F813 mov eax, 6 +.text:000000018009F818 test byte ptr ds:7FFE0308h, 1 ;判断是否支持快速调用 +.text:000000018009F820 jnz short loc_18009F825 +.text:000000018009F822 syscall ; Low latency system call +.text:000000018009F824 retn +.text:000000018009F825 ; --------------------------------------------------------------------------- +.text:000000018009F825 +.text:000000018009F825 loc_18009F825: ; CODE XREF: NtReadFile+10↑j +.text:000000018009F825 int 2Eh ; DOS 2+ internal - EXECUTE COMMAND +.text:000000018009F825 ; DS:SI -> counted CR-terminated command string +.text:000000018009F827 retn +.text:000000018009F827 NtReadFile endp +``` + +上面的代码使用的是win10 x64 ntdll.dll + +> x64系统使用的是`fastcall`调用约定,参数1、2、3、4依次存放在rcx,rdx,r8d,r9d,剩下的参数从右往左依次压入堆栈。 + + +在调用这个函数时,所有的参数已经压入了寄存器或堆栈,rsp指向最新的栈顶。 + +`mov eax,6`,这个6其实是系统调用表的编号,不管是使用`syscall`还是`int 0x2e`进入内核,在进入内核后,内核会计算出参数存放位置和堆栈中参数的位置(rsp),最后根据参数执行完函数,通过自陷返回应用层。 + +显然,所有系统调用都具有上述相同的样式,不同的只是系统调用号和参数的个数。 + +## 内核中的 NtReadFile + +不光在ntdll中有NtReadFile这个函数,在内核中也有一个同名函数和其对应。 + +当使用自陷指令进入内核后,通过系统调用表找到的就是内核中的NtReadFile +``` + +`Windows Driver Development/sc.md`: + +```md +# sc 服务管理 + +## 安装驱动 + +```shell +sc create SERVICE_NAME binPath= "SERVICE_PATH" type= kernel start= auto +``` + +**注:binpath、type、start等号后面的空格,一定要有** + + + +## 启动驱动 + +```shell +sc start SERVICE_NAME +``` + + + + + +## 停止驱动 + +```shell +sc stop SERVICE_NAME +``` + + + + + +## 获取驱动状态 + +```shell +sc query SERVICE_NAME +``` + + + + + +## 卸载驱动 + +```shell +sc delete SERVICE_NAME +``` + + + + +``` + +`Windows Driver Development/windows禁用驱动签名.md`: + +```md +# 禁用驱动签名 + +管理员方式启动cmd + + + +```shell +bcdedit.exe /set testsigning on +``` + + + +## 启动 + + + +```shell +bcdedit.exe /set testsigning off +``` + + +``` + +`Windows Driver Development/wow64运行原理.md`: + +```md +## 32位程序是怎样在windows 64 上运行的? + +由于win64内核是64位的,所以32位程序运行时环境在与64位内核时需要有状态转换。 + +接下来进行分析,可以在`C:/windows/syswow64`中发现许多和`C:/windows/system32`下同名的动态库,如`kernel32.dll`,`ntdll.dll`,`msvcrt.dll`,`ws2_32.dll`等,其实这些都是32位的版本。像wow64名字所表达的含义一样,syswow64这些库相当于在64位windows中构建了一个32位windows子系统环境,我们32位的程序能正常在win64上运行正是靠这个子环境负责与64位环境进行了交互和兼容。 + +// https://bbs.pediy.com/thread-221236.htm + +``` + +`Windows Driver Development/内核sleep等待.md`: + +```md + + +```C +NTSTATUS KeDelayExecutionThread( + KPROCESSOR_MODE WaitMode, + BOOLEAN Alertable, + PLARGE_INTEGER Interval +); +``` +参数`Interval`: + +指定等待发生的绝对或相对时间(以100纳秒为单位)。负值表示相对时间。绝对过期时间跟踪系统时间的任何变化;相对过期时间不受系统时间更改的影响。 + +``` +1秒 = 1000毫秒 = 1000000000纳秒 + +1毫秒 = 1000000纳秒 +``` + +常用转换函数`RtlConvertLongToLargeInteger`将long转为largeinteger + + +``` + +`Windows Driver Development/内核模式下的字符操作.md`: + +```md +# 内核模式下的字符操作 + +DDK不鼓励使用c的字符串,所以进行了封装 + +```C +typedef struct _STRING {   + USHORT Length;   + USHORT MaximumLength;   + PCHAR Buffer; +} STRING; +typedef STRING ANSI_STRING; +typedef PSTRING PANSI_STRING +``` +- Length:字符的长度。 +- MaximumLength:整个字符串缓冲区的最大长度。 +- Buffer:缓冲区的指针。 + +```C +typedef struct _UNICODE_STRING {   + USHORT Length;   + USHORT MaximumLength;   + PWSTR Buffer; +} UNICODE_STRING; +``` +- Length:字符的长度,单位是字节。如果是N个字符,那么Length等于N的2倍。 +- MaximumLength:整个字符串缓冲区的最大长度,单位也是字节。 +- Buffer:缓冲区的指针 + +**封装的字符串和普通的不同,它的长度是靠MaximumLength决定,而不是长度-1,最后一个为NULL** + + +## 字符串初始化 +```C +VOID   +RtlInitAnsiString(    + IN OUT PANSI_STRING DestinationString,    + IN PCSZ SourceString ) +//打印AnsiString使用%Z +//打印UnicodeString使用%wZ +VOID   +RtlInitUnicodeString(    + IN OUT PUNICODE_STRING DestinationString,    + IN PCWSTR SourceString ); +``` +- __这中办法是将AnsiString1中的Buffer等于string1指针。这种初始化的优点是 +操作简单,用完后不用清理内存。但带来另外一个问题,如果修改string1,同时 +会导致AnsiString1字符串发生变化__ +```C +ANSI_STRING AnsiString1; +CHAR* string1 = "hello"; +//初始化ANSI_STRING字符串 +RtlInitAnsiString(&AnsiString1,string1); +KdPrint(("AnsiString1:%Z\n", &AnsiString1));//打印hello +//改变string1 +string1[0]='H'; +string1[1]='E'; +string1[2]='L'; +string1[3]='L'; +string1[4]='o'; +//改变string1,AnsiString1同样会导致变化 +KdPrint(("AnsiString1:%Z\n", &AnsiString1));//打印HELLO +``` + +```C +ANSI_STRING ansiString; +//省去对ansiString初始化 +KdPrint("%Z\n", &ansiString); +//打印UNICODE_STRING字符串: +UNICODE_STRING uniString; +//省去对uniString初始化 +KdPrint("%wZ\n", &uniString) +``` + +- __另外一种方法是程序员自己申请内存,并初始化内存,当不用字符串时 +需要回收字符串占用的内存__ +```C +#define BUFFER_SIZE 1024 +UNICODE_STRING UnicodeString1 = {0}; +//设置缓冲区大小 +UnicodeString1.MaximumLength = BUFFER_SIZE; +//分配内存 +UnicodeString1.Buffer = (PWSTR)ExAllocatePool(PagedPool, BUFFER_SIZE); +WCHAR* wideString = L"hello"; +//设置字符串长度,因为是宽字符,所以是字符长度的2倍 +UnicodeString1.Length = 2 * wcslen(wideString); +//保证缓冲区足够大,否则程序终止 +ASSERT(UnicodeString1.MaximumLength >= UnicodeString1.Length); +//内存赋值 +RtlCopyMemory(UnicodeString1.Buffer, wideString, UnicodeString1.Length); + +KdPrint(("UnicodeString:%wZ\n", &UnicodeString1)); + +//清理内存 +ExFreePool(UnicodeString1.Buffer); +UnicodeString1.Buffer = NULL; +UnicodeString1.Length = UnicodeString1.MaximumLength = 0; + +//清理内存,DDK同样给出了简化函数分别是 + +RtlFreeAnsiString +RtlFreeUnicodeString +``` + +## 字符串复制 +```C +/************************************************************************ +* 函数名称:RtlCopyString +* 功能描述:ANSI_STRING字符串复制 +* 参数列表: + DestinationString:目的字符串 + SourceString:源字符串 +* 返回 值:VOID +*************************************************************************/ +VOID RtlCopyString( + IN OUT PSTRING DestinationString, + IN PSTRING SourceString OPTIONAL); + +/************************************************************************ +* 函数名称:RtlCopyUnicodeString +* 功能描述:UNICODE_STRING字符串复制 +* 参数列表: + DestinationString:目的字符串 + SourceString:源字符串 +* 返回 值:VOID +*************************************************************************/ +VOID RtlCopyUnicodeString( + IN OUT PUNICODE_STRING DestinationString, + IN PUNICODE_STRING SourceString OPTIONAL); + + +//初始化UnicodeString1 +UNICODE_STRING UnicodeString1; +RtlInitUnicodeString(&UnicodeString1, L"Hello World"); + +//初始化UnicodeString2 +UNICODE_STRING UnicodeString2 = {0}; +UnicodeString2.Buffer = (PWSTR)ExAllocatePool(PagedPool, BUFFER_SIZE); +UnicodeString2.MaximumLength = BUFFER_SIZE; + +//将初始化UnicodeString1复制到UnicodeString2 +RtlCopyUnicodeString(&UnicodeString2, &UnicodeString1); + +//分别显示UnicodeString1和UnicodeString2 +KdPrint(("UnicodeString1%wZ\n", &UnicodeString1)); +KdPrint(("UnicodeString2%wZ\n", &UnicodeString2)); + +//销毁UnicodeString2 +//注意UnicodeString1不用销毁 +RtlFreeUnicodeString(&UnicodeString2); +``` +``` + +`Windows Driver Development/分层驱动.md`: + +```md +# 分层驱动 + +分层驱动是指两个或两个以上的驱动程序,它们分别创建设备对象,一层一层地“挂载”在其他设备对象之上,并且形成一个 **由高到低的设备对象栈**。 +**IRP请求一般会被传送到设备栈的最顶层的设备对象,顶层的设备对象可以选择直接结束IRP请求,也可以选择将IRP请求向下层的设备对象转发。** +**如果是向下层设备对象转发IRP请求,当IRP请求结束时,IRP会顺着设备栈的反方向原路返回。** +**当得知下层驱动程序已经结束IRP请求时,本层设备对象可以选择继续将IRP向上返回,或者选择重新将IRP再次传递给底层设备驱动。** + +在`DEVICE_OBJECT`结构体中有AttachedDevice这个参数,记录了自己这个设备**被**哪一个设备对象所挂载。 + +![local image](./images/1543485831(1).jpg) + +图中的驱动HidUsb下的设备000000a9被驱动mouhid的一个设备所挂载,这个设备的地址是箭头起始处。 + +## 设备堆栈和挂载 + +> 挂载是将高一层的设备对象挂载在低一层的设备对象上,从而形成一个设备栈。 + +相关函数有`IoAttachDeviceToDeviceStack`、`IoAttachDevice` + +当驱动程序卸载的时候,驱动程序所创建的设备对象应该从设备栈中弹出。出栈的顺序必须和入栈的顺序相反。 + +```C +VOID  IoDetachDevice(   IN OUT PDEVICE_OBJECT TargetDevice   ); +``` + +**TargetDevice :它是位于被删除设备对象的低一层设备对象。** + +## I/O堆栈 + +在驱动模型中,有一个概念叫做`I/O堆栈`,用`IO_STACK_LOCATION`数据结构表示。它和设备栈紧密联合。 + +IRP一般会由应用程序的readfile或writefile创建,然后发送到设备堆栈的顶层。如果最上层的设备不处理IRP,就会将IRP转发到下一层设备。每一层设备都有可能处理IRP。IRP每穿越一层设备,就会用`IO_STACK_LOCATION`记录下本次操作的某些属性。 + +IRP中有个字段`CurrentLocation`,I/O管理器为一个设备对象堆栈准备IRP时将其设置成`stacksize+1`,即堆栈深度加1。 + +如果当前设备栈只有一个设备,那么在irp初始的时候,`CurrentLocation`会设置为2,因为IO管理器在调用iocalldriver函数的时候会将它减1。 + +当IRP初始化的时候,`stacksize`是多少,那么`IO_STACK_LOCATION`数组大小就是多少,并且在`CurrentStackLocation`指向数组结尾。在调用iocalldriver的时候,它也会减1。 + +```C++ +VOID +NTAPI +IoInitializeIrp(IN PIRP Irp, + IN USHORT PacketSize, + IN CCHAR StackSize) +{ + /* Clear it */ + IOTRACE(IO_IRP_DEBUG, + "%s - Initializing IRP %p\n", + __FUNCTION__, + Irp); + RtlZeroMemory(Irp, PacketSize); + + /* Set the Header and other data */ + Irp->Type = IO_TYPE_IRP; + Irp->Size = PacketSize; + Irp->StackCount = StackSize; + Irp->CurrentLocation = StackSize + 1; + Irp->ApcEnvironment = KeGetCurrentThread()->ApcStateIndex; + Irp->Tail.Overlay.CurrentStackLocation = (PIO_STACK_LOCATION)(Irp + 1) + StackSize; + + /* Initialize the Thread List */ + InitializeListHead(&Irp->ThreadListEntry); +} + +NTSTATUS +FASTCALL +IofCallDriver(IN PDEVICE_OBJECT DeviceObject, + IN PIRP Irp) +{ + PDRIVER_OBJECT DriverObject; + PIO_STACK_LOCATION StackPtr; + + /* Make sure this is a valid IRP */ + ASSERT(Irp->Type == IO_TYPE_IRP); + + /* Get the Driver Object */ + DriverObject = DeviceObject->DriverObject; + + /* Decrease the current location and check if */ + Irp->CurrentLocation--; + if (Irp->CurrentLocation <= 0) + { + /* This IRP ran out of stack, bugcheck */ + KeBugCheckEx(NO_MORE_IRP_STACK_LOCATIONS, (ULONG_PTR)Irp, 0, 0, 0); + } + + /* Now update the stack location */ + StackPtr = IoGetNextIrpStackLocation(Irp); + Irp->Tail.Overlay.CurrentStackLocation = StackPtr; + + /* Get the Device Object */ + StackPtr->DeviceObject = DeviceObject; + + /* Call it */ + return DriverObject->MajorFunction[StackPtr->MajorFunction](DeviceObject, + Irp); +} + +__drv_aliasesMem +FORCEINLINE +PIO_STACK_LOCATION +IoGetNextIrpStackLocation( + _In_ PIRP Irp) +{ + ASSERT(Irp->CurrentLocation > 0); +#ifdef NONAMELESSUNION + return ((Irp)->Tail.Overlay.s.u.CurrentStackLocation - 1 ); +#else + return ((Irp)->Tail.Overlay.CurrentStackLocation - 1 ); +#endif +} + +``` + +## 向下转发IRP + +当顶层驱动的设备对象收到IRP请求并进入派遣函数后,有多种选择方式处理IRP + +- 直接处理该IRP,即调用`IoCompleteRequest`内核函数。 +- 调用`StartIO`,操作系统会将IRP请求串行化。除了当前运行的IRP,其他的IRP请求进入IRP队列。 +- 选择让底层驱动完成IRP。 + +IRP内部有个指针指向当前正在使用的IO_STACK_LOCATION,可以使用内核宏`IoGetCurrentIrpStackLocation`获得当前I/O堆栈。 + +每次调用`IoCallDriver`时,内核函数都会将IRP的当前指针下移,指向下一个IO_STACK_LOCATION指针。如果当前设备堆栈不对IRP做任何处理。因此,当前设备就不需要对应I/O堆栈。 + +**但是`IoCallDriver`已经将当前I/O堆栈向下移动了一个单位**,所以提供`IoSkipCurrentIrpStackLocation`,它的作用是将当前I/O堆栈又往回(上)移动一个单位。这样这两个函数对设备堆栈的移动就实现了平衡,也就是没有改变。这时`IoCallDriver`调用的低一层驱动所用到的I/O堆栈,实际上和本层用到的是同一个。因此,当本层驱动不需要用I/O堆栈时,可以如下操作: + +```C +PDEVICE_EXTENSION pdx =(PDEVICE_EXTENSION)pDevObj->DeviceExtension; + +//调用底层驱动 +IoSkipCurrentIrpStackLocation (pIrp); +ntStatus =IoCallDriver(pdx->TargetDevice, pIrp); +``` + +## 设备堆栈的组成 + +栈的底部是一个物理设备对象(PDO),它与总线驱动程序相关联。设备通常附加到标准硬件总线,如PCI或USB。 + +设备堆栈的核心是功能设备对象(FDO) + +过滤驱动程序和过滤设备对象 设备堆栈可以有多个过滤设备对象,可以放在FDO的上面或下面。 + +## Device Tree + +在系统启动时,PnP管理器从最低级总线开始,执行以下操作: + +1. 加载总线驱动,该驱动程序枚举连接到其物理总线上的每个设备的PDO,并为这些设备请求资源。 +2. 为每个设备分配资源 +3. 查询其数据库以确定与每个设备相关联的驱动程序。 +4. 在每个PDO之上构造一个设备堆栈 +5. 开始每个设备 +6. 查询每个设备可能枚举的任何PDO +7. 根据需要重复2-6 + + +``` + +`Windows Driver Development/分层驱动IRP探究.md`: + +```md +# 分层驱动IRP探究 + +I/O管理器会创建一个IRP堆栈,堆栈中的元素分别对应于分层驱动中的设备对象。每个设备拥有IRP堆栈中的一个位置,可以使用`IoGetCurrentIrpStackLocation`获取当前IRP信息。 + +例子是一个TDI驱动,驱动程序创建一个设备对象attach到Tcp设备对象上。 + +设备堆栈如下: +``` +kd> !devstack 0xfffffa80`058b1b70 + !DevObj !DrvObj !DevExt ObjectName +> fffffa80058b1b70 \Driver\TdiDriverTest00000000 + fffffa80044fd060 \Driver\tdx fffffa80044fd1b0 Tcp +``` +看一看`IoSkipCurrentIrpStackLocation(Irp)`和`IoCallDriver(OldObj, Irp)`究竟做了什么? + +查看一下IRP: +``` +kd> !irp 0xfffffa80`06cf8e10 +Irp is active with 2 stacks 2 is current (= 0xfffffa8006cf8f28) + No Mdl: No System Buffer: Thread fffffa8003d27040: Irp stack trace. + cmd flg cl Device File Completion-Context + [N/A(0), N/A(0)] + 0 0 00000000 00000000 00000000-00000000 + + Args: 00000000 00000000 00000000 00000000 +>[IRP_MJ_INTERNAL_DEVICE_CONTROL(f), N/A(2)] + 0 e0 fffffa80058b1b70 fffffa80055f6dd0 fffff88004cf6a90-00000000 Success Error Cancel + \Driver\TdiDriverTest afd!AfdRestartDeviceControl + Args: 00000000 00000000 0x3 00000000 +``` +发现`fffffa80058b1b70`正好是驱动程序TdiDriverTest创建的设备对象。当前IRP给这个设备对象发送了`IRP_MJ_INTERNAL_DEVICE_CONTROL`指令。 + +当执行一次`IoSkipCurrentIrpStackLocation(Irp)`后,再次查看: + +发现`>`已经不在, + +调用IoCallDriver后,传递给IoCallDriver的IRP指针不再有效,无法安全地解除引用。 +``` + +`Windows Driver Development/取消IRP.md`: + +```md +# 取消IRP + +如果不使用`IoCompleteRequest`函数结束的话,也可以使用`IoSetCancelRoutine`结束,也就是取消IRP请求。 + +``` +PDRIVER_CANCEL IoSetCancelRoutine( + PIRP Irp, + PDRIVER_CANCEL CancelRoutine +); +``` +`CancelRoutine`这个是取消函数的函数指针。 + +IoSetCancelRoutine可以将一个取消函数与该IRP关联,一旦取消IRP请求的时候,这个取消函数会被执行。IoSetCancelRoutine函数也可以用来删除取消函数,当输入的Cancelroutine参数为NULL时,则删除原来的取消函数。 + +``` + +`Windows Driver Development/字符串操作.md`: + +```md +## 字符串的初始化 + +`RtlInitUnicodeString` + +## 字符串的拷贝 + +以字节计数将一个字符串拷贝到缓冲区,注意第二个参数是目标缓冲区的大小 +`RtlStringCbCopyA`和`RtlStringCbCopyW` + + +`RtlCopyUnicodeString` + +连接两个字符串,按字节计数,第二个参数是目标缓冲区的大小 +`RtlStringCbCatW`和`RtlStringCbCatA` + + +``` + +`Windows Driver Development/完成例程.md`: + +```md +# 完成例程 + +在将IRP发送给底层驱动或者其他驱动前,可以对IRP设置一个完成例程。一旦底层驱动将IRP完成后,IRP完成例程立刻被触发。 + +``` + +`Windows Driver Development/对象管理.md`: + +```md +# 对象管理 + + +## 对象 + +这里的对象和`面向对象程序设计`所说的对象不一样。在Windows内核中,“对象”实际上就是数据结构,就是一些带有“对象头(Object Head)”的特殊数据结构。 + + +任何对象的数据结构都由对象头和具体对象类型的数据结构两部分构成。 + +![对象数据结构图](./images/1576813763(1).jpg) + + +## 对象目录 + +参考 Winobj 这个软件去理解: + +对象目录是多个“节点”连接而成的树状结构,树的极好是一个“目录”对象,即类型为`OBJECT_DIRECTORY`的对象。树中的每个节点都是对象,所以“节点名”就是“对象名”。除根节点之外,树中的所有中间节点都必须是目录对象或“符号链接”对象(即类型为`OBJECT_SYMBOLIC_LINK`的对象),而普通的对象则只能成为“叶节点”。 + + +## 对象类型 + +对象是分类的,因而是有“类型(Type)”的。Windows对象类型的集合(对于内核)是开放的,内核函数可以向这个集合中加入新的对象类型。用户可以把内核模块即.sys模块动态安装到内核中,使其成为内核的一部分。这样,用户就实际上有了增加新对象的自由。 + +Windows内核为新对象类型的定义提供了一个全局的`OBJECT_TYPE_INITIALIZE`数据结构。 + +要定义即创建一种新的对象类型时,就需要填写好一个`OBJECT_TYPE_INITIALIZE`数据结构,然后调用`ObCreateObjectType()`。这个函数根据结构创建起新的对象类型的数据结构,然后将其挂入对象目录。对象类型的数据结构是`OBJECT_TYPE`,其内部有个成员`TypeInfo`也是`OBJECT_TYPE_INITIALIZE`数据结构,“申请单”的内容将被复制到`OBJECT_TYPE`数据结构内部的`TypeInfo`中。 + + +## 句柄和句柄表 + + +``` + +`Windows Driver Development/文件过滤驱动/和老C一起学透明加解密.md`: + +```md +本系列建立在驱网开源的filter代码之上。感谢老钱的开源,感谢liimee和安于此生两位大兄弟几乎同时把代码传给我。感谢! + +由于文件过滤驱动比较麻烦,我会尽量用一个月的时间将此系列写完。开始于2019年7月17日,暂预定2019年8月17日结束。 + +``` + +`Windows Driver Development/注册表保护.md`: + +```md +使用 `CmRegisterCallback` 函数 + +驱动程序调用`CmRegisterCallback`来注册RegistryCallback例程,每次线程在注册表上执行操作时都会调用该例程。 + +调用`CmUnRegisterCallback` 来注销回调例程。 + +```C++ + RtlInitUnicodeString( &RoutineName, L"CmRegisterCallback" ); + *( PVOID* )&KrnlCmRegisterCallback = MmGetSystemRoutineAddress( &RoutineName ); + + RtlInitUnicodeString( &RoutineName, L"CmUnRegisterCallback" ); + *( PVOID* )&KrnlCmUnRegisterCallback = MmGetSystemRoutineAddress( &RoutineName ); +``` + +函数声明: +```C++ +NTSTATUS CmRegisterCallback( + PEX_CALLBACK_FUNCTION Function, + PVOID Context, + PLARGE_INTEGER Cookie +); + +EX_CALLBACK_FUNCTION ExCallbackFunction; + +NTSTATUS ExCallbackFunction( + PVOID CallbackContext, + PVOID Argument1, + PVOID Argument2 +) +{...} +``` + +`Argument1`表示了正在执行的注册表操作类型,对此参数的各种情况做switch处理,来控制注册表的权限。 + +例如:`RegNtDeleteKey`、`RegNtPreDeleteKey`、`RegNtPreSetValueKey`等 +``` + +`Windows Driver Development/注册表操作.md`: + +```md +## 创建注册表 + +ZwCreateKey不仅可以创建,也可以打开,在没有此项的时候会创建。 +``` +NTSYSAPI NTSTATUS ZwCreateKey( + PHANDLE KeyHandle, //返回注册表的句柄 + ACCESS_MASK DesiredAccess, //访问权限,一般为 KEY_ALL_ACCESS + POBJECT_ATTRIBUTES ObjectAttributes,//ob数据结构 + ULONG TitleIndex, //一般不用,填0 + PUNICODE_STRING Class, //一般不用,填NULL + ULONG CreateOptions, //创建时的选项,一般为:REG_OPTION_NON_VOLATILE + PULONG Disposition //返回是创建成功还是打开成功,REG_CREATE_NEW_KEY或REG_OPENED_EXISTING_KEY +); +``` +**打开子项是在`InitializeObjectAttributes`的root参数填写主项的句柄即可。** + +## 打开注册表 + +如果ZwOpenKey指定的项不存在,不会创建这个项目,而是返回一个错误状态。 + +``` +NTSYSAPI NTSTATUS ZwOpenKey( +PHANDLE KeyHandle, //返回被打开的句柄 +ACCESS_MASK DesiredAccess, //访问权限,一般为 KEY_ALL_ACCESS +POBJECT_ATTRIBUTES ObjectAttributes //ob数据结构 +); +``` + +## 添加或修改注册表键值 + +``` +NTSYSAPI NTSTATUS ZwSetValueKey( + HANDLE KeyHandle, //注册表句柄 + PUNICODE_STRING ValueName, //要新建或者修改的键名 + ULONG TitleIndex, //一般不用,填0 + ULONG Type, //键值的类型 + PVOID Data, //数据 + ULONG DataSize //数据大小 +); +``` + +使用ZwSetValueKey的时候,如果指定的键名不存在,则直接创建;如果存在,则对已有键值进行修改。 + +**在使用类型为REG_SZ的时候,数据长度是字符串长度的二倍加上两个字节** + + +## 查询注册表 + +``` +NTSYSAPI NTSTATUS ZwQueryValueKey( + HANDLE KeyHandle, //打开的注册表的句柄 + PUNICODE_STRING ValueName, //要查询的键名 + KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass, //根据KeyValueInformation的不同选择不同的查询类别 + PVOID KeyValueInformation, //选择一种查询类别 + ULONG Length, //要查询的数据长度 + PULONG ResultLength //实际查询数据的长度 +); +``` + + +``` + +`Windows Driver Development/设备驱动框架.md`: + +```md +## Windows的设备驱动框架 + +Windows内核管理层的部件之一是I/O管理模块,也叫I/O子系统。I/O管理模块所管理的对象与活动纵向贯穿**管理层、核心层乃至HAL层**。 + +I/O管理的主体就是我们所说的设备驱动。 + + +一个设备的驱动软件常常表现为若干驱动程序的“堆叠”。这个堆叠的顶层在管理层中,底层则在HAL层中;越往上,离具体设备的硬件越远,就越抽象,与其他设备的共性就越多。越往下,离具体设备的硬件越近,就越具体、越体现出具体设备的个性。 + + +**上层模块与下层模块之间不一定是一对一的关系,而可以是一对多的关系。以文件系统为例,这个堆叠的顶层大致是文件系统,下层是文件系统所在的载体。这个载体可以是磁盘,也可以是光盘,还可以是U盘。所以,设备驱动逻辑意义上的系统结构其实是一种(倒置的)树状结构** + + +DPC函数:延迟过程请求 Delayed Procedure Call + +设备驱动的堆叠是可以嵌套的。什么是嵌套?以USB鼠标为例:自上而下,鼠标的类驱动-->USB鼠标的端口驱动-->USB设备的类驱动-->USB接口芯片的端口驱动。这样就把两个堆叠嵌套在一起。 + + +端口驱动是直接与设备打资产的,所以又称为“物理驱动”,而代表着物理驱动模块的“对象”就称为PDO Physical Device Object。由于物理的接口总是在某个总线上,所以又有可能称为`总线驱动`。上层那些`中间驱动`中的`类驱动`,则又称为`功能驱动`相应的对象则称为`FDO`Funcitonal Device Object。 + + +在具体的实现上,每个设备驱动由`驱动对象`,即`Driver Obecjt`、和`设备对象`,即`Device Object`组成。 + +- Driver Object 主要用来提供一组函数指针 +- Device Object 用来记录具体设备的各种状态,并实现形式上的堆叠。 + + +上下层的堆叠是通过设备对象结构中的指针`AttachedDevice`指向其直接上层的设备对象,而上层的设备对象则通过某个指针反过来指向这个设备对象。这个指针需要自己去构造,自己在创建设备的时候,创建设备扩展。 + +如果从最底层的模块开始,自底向上,顺着各个设备对象中的`AttachedDevice`指针上溯,是可以遍历整个堆叠的。反过来要遍历就需要设备扩展了。 + +有意思的一点: + +> 设备对象扩充部的头部是`EXTENDED_DEVOBJ_EXTENSION`而不是`DEVOBJ_EXTENSION`时,里面就有个指针`AttachedTo`,这就是向下的指针。事实上通过内核函数`IoCreateDevice`创建的设备对象都以`EXTENDED_DEVOBJ_EXTENSION`作为扩充部的头部,但microsoft没有保证,建议不用。 + +**注意:设备头扩展和设备扩展不是一个东西,这两者均在`DEVICE_OJBECT`结构中定义** + + +当上层驱动模块要调用下层驱动模块时,就准备一个IRP并调用内核函数`IoCallDriver`或`IofCallDriver`,此时,程序就根据IRP中的操作码转入了目标模块所提供的某个函数。 + + +`DRIVER_OBJECT`这个数据结构代表了所有驱动模块属性中公共的、最基本的那一部分。而反映不同模块特性的那一部分,则需要存放在附属的数据结构中,首先就是`DRIVER_EXTENSION`。 + +在`DRIVER_EXTENSION`结构中有`AddDevice`函数指针,此函数为了PNP设备而加的,目的是将本模块的设备对象堆叠到另一个设备对象的上面,从而将其加入某个堆叠中的任务。 + +``` + +`Windows Driver Development/进程监控.md`: + +```md +# 进程监控 + +PsSetCreateProcessNotifyRoutine + +PsSetLoadImageNotifyRoutine + +``` + +`Windows Driver Development/驱动加载顺序.md`: + +```md +## 启动顺序 + +一般情况下有以下几种: + +- SERVICE_BOOT_START (0) + +> SERVICE_BOOT_START (0x00000000) Indicates a driver started by the operating system (OS) loader + +- SERVICE_SYSTEM_START (1) + +> SERVICE_SYSTEM_START (0x00000001) Indicates a driver started during OS initialization. + +- SERVICE_AUTO_START (2) + +> SERVICE_AUTO_START (0x00000002) Indicates a driver started by the Service Control Manager during system startup. + +- SERVICE_DEMAND_START (3) + +> SERVICE_DEMAND_START (0x00000003) Indicates a driver started on demand + +- SERVICE_DISABLED (4) + +> SERVICE_DISABLED (0x00000004) Indicates a driver that is not started by the OS loader, Service Control Manager, or PnP Manager + +除了以上的 start type可以控制启动顺序,还可以通过别的注册表项进行更加细致的微调 + +在路径:`计算机\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Hookport`下,其start设置为0启动,还有一个Group项,用来排序所有0启动类型驱动的启动顺序。 + +![1](./images/11160817_1FOS.jpg) + +路径:`计算机\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\ServiceGroupOrder`下有一个List用来排序同一启动类型的启动顺序 + +![2](./images/11160817_puN0.jpg) + +当上述两项也相同时,路径:`计算机\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GroupOrderList`,根据图1中的`Tag`项对同一`Group`排序 + +![3](./images/1565934691(1).jpg) +![3.1](./images/11160817_O21Y.jpg) + + +``` + +`Windows Driver Development/驱动常用函数.md`: + +```md +# 驱动常用函数 + +## 字符串 + +字符串分ASCII字符串和UNICODE字符串 + +```C++ +typedef struct _STRING { + USHORT Length; + USHORT MaximumLength; + PCHAR Buffer; +} STRING; +typedef STRING ANSI_STRING; +typedef PSTRING PANSI_STRING; + +typedef struct _UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; +} UNICODE_STRING; +``` + +大部分上都使用UNICODE字符串 + +### 字符串初始化 + +```C++ +void RtlInitUnicodeString( + PUNICODE_STRING DestinationString, + PCWSTR SourceString +); +``` + +### 字符串复制 + +```C++ +NTSYSAPI VOID RtlCopyUnicodeString( + PUNICODE_STRING DestinationString, + PCUNICODE_STRING SourceString +); +``` + +### 字符串比较 + +```C++ +NTSYSAPI LONG RtlCompareUnicodeString( + PCUNICODE_STRING String1, + PCUNICODE_STRING String2, + BOOLEAN CaseInSensitive +); +``` + +### 字符串转化成大写 + +```C++ +NTSYSAPI NTSTATUS RtlUpcaseUnicodeString( + PUNICODE_STRING DestinationString, + PCUNICODE_STRING SourceString, + BOOLEAN AllocateDestinationString +); +``` + +### 字符串与整型数字相互转换 + +```C++ +NTSYSAPI NTSTATUS RtlUnicodeStringToInteger( + PCUNICODE_STRING String, + ULONG Base, + PULONG Value +); +``` + +### ANSI_STRING字符串与UNICODE_STRING字符串相互转换 + +```C++ +NTSTATUS RtlUnicodeStringToAnsiString( + PANSI_STRING DestinationString, + PCUNICODE_STRING SourceString, + BOOLEAN AllocateDestinationString +); + +NTSYSAPI NTSTATUS RtlAnsiStringToUnicodeString( + PUNICODE_STRING DestinationString, + PCANSI_STRING SourceString, + BOOLEAN AllocateDestinationString +); +``` + +> AllocateDestinationString:是否需要对被转换的字符串分配内存。如果为FALSE的话,目的字符串的buffer需要自己分配内存和释放。如果为TRUE的话,是由本函数分配的内存,需要使用`RtlFreeUnicodeString`释放内存。 + +## 文件操作 + +### 文件创建 + +```C++ +__kernel_entry NTSYSCALLAPI NTSTATUS NtCreateFile( + PHANDLE FileHandle, + ACCESS_MASK DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes, + PIO_STATUS_BLOCK IoStatusBlock, + PLARGE_INTEGER AllocationSize, + ULONG FileAttributes, + ULONG ShareAccess, + ULONG CreateDisposition, + ULONG CreateOptions, + PVOID EaBuffer, + ULONG EaLength +); +``` + +```C++ +VOID InitializeObjectAttributes( + [out] POBJECT_ATTRIBUTES InitializedAttributes, + [in] PUNICODE_STRING ObjectName, + [in] ULONG Attributes, + [in] HANDLE RootDirectory, + [in, optional] PSECURITY_DESCRIPTOR SecurityDescriptor +); +``` + +### 文件打开 + +```C++ +__kernel_entry NTSYSCALLAPI NTSTATUS NtOpenFile( + PHANDLE FileHandle, + ACCESS_MASK DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes, + PIO_STATUS_BLOCK IoStatusBlock, + ULONG ShareAccess, + ULONG OpenOptions +); +``` + +### 获取或修改文件属性 + +```C++ +NTSYSAPI NTSTATUS ZwSetInformationFile( + HANDLE FileHandle, + PIO_STATUS_BLOCK IoStatusBlock, + PVOID FileInformation, + ULONG Length, + FILE_INFORMATION_CLASS FileInformationClass +); +``` + +```C++ +__kernel_entry NTSYSCALLAPI NTSTATUS NtQueryInformationFile( + HANDLE FileHandle, + PIO_STATUS_BLOCK IoStatusBlock, + PVOID FileInformation, + ULONG Length, + FILE_INFORMATION_CLASS FileInformationClass +); +``` + +### 文件的写操作 + +```C++ +__kernel_entry NTSYSCALLAPI NTSTATUS NtWriteFile( + HANDLE FileHandle, + HANDLE Event, + PIO_APC_ROUTINE ApcRoutine, + PVOID ApcContext, + PIO_STATUS_BLOCK IoStatusBlock, + PVOID Buffer, + ULONG Length, + PLARGE_INTEGER ByteOffset, + PULONG Key +); +``` + +### 文件的读操作 + +```C++ +__kernel_entry NTSYSCALLAPI NTSTATUS NtReadFile( + HANDLE FileHandle, + HANDLE Event, + PIO_APC_ROUTINE ApcRoutine, + PVOID ApcContext, + PIO_STATUS_BLOCK IoStatusBlock, + PVOID Buffer, + ULONG Length, + PLARGE_INTEGER ByteOffset, + PULONG Key +); +``` + +## 注册表 + +### 创建打开Key + +```C++ +NTSYSAPI NTSTATUS ZwCreateKey( + PHANDLE KeyHandle, + ACCESS_MASK DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes, + ULONG TitleIndex, + PUNICODE_STRING Class, + ULONG CreateOptions, + PULONG Disposition +); +``` + +> Disposition:返回是创建成功,还是打开成功。返回值是REG_CREATED_NEW_KEY或者是 REG_OPENED_EXISTING_KEY。 + +### 打开注册表 + +```C++ +NTSYSAPI NTSTATUS ZwOpenKey( + PHANDLE KeyHandle, + ACCESS_MASK DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes +); +``` + +### 添加修改注册表键值 + +```C++ +NTSYSAPI NTSTATUS ZwSetValueKey( + HANDLE KeyHandle, + PUNICODE_STRING ValueName, + ULONG TitleIndex, + ULONG Type, + PVOID Data, + ULONG DataSize +); +``` + +### 查询注册表 + +```C++ +NTSYSAPI NTSTATUS ZwQueryValueKey( + HANDLE KeyHandle, + PUNICODE_STRING ValueName, + KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass, + PVOID KeyValueInformation, + ULONG Length, + PULONG ResultLength +); +``` + +### 枚举子项 + +```C++ +NTSYSAPI NTSTATUS ZwQueryKey( + HANDLE KeyHandle, + KEY_INFORMATION_CLASS KeyInformationClass, + PVOID KeyInformation, + ULONG Length, + PULONG ResultLength +); +``` + +```C++ +NTSYSAPI NTSTATUS ZwEnumerateKey( + HANDLE KeyHandle, + ULONG Index, + KEY_INFORMATION_CLASS KeyInformationClass, + PVOID KeyInformation, + ULONG Length, + PULONG ResultLength +); +``` + +> ZwQueryKey的作用主要是获得某注册表项究竟有多少个子项,而ZwEnumerateKey的作用主要是针对 第几个子项获取该子项的具体信息。 + +### 枚举子键 + +```C++ +NTSYSAPI NTSTATUS ZwEnumerateValueKey( + HANDLE KeyHandle, + ULONG Index, + KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass, + PVOID KeyValueInformation, + ULONG Length, + PULONG ResultLength +); +``` + +### 删除子项 + +```C++ +NTSYSAPI NTSTATUS ZwDeleteKey( + HANDLE KeyHandle +); +``` + +## MiniFilter + +### 上下文 + +#### 文件上下文 + +```C++ +NTSTATUS FLTAPI FltAllocateContext( + PFLT_FILTER Filter, + FLT_CONTEXT_TYPE ContextType, + SIZE_T ContextSize, + POOL_TYPE PoolType, + PFLT_CONTEXT *ReturnedContext +); + +NTSTATUS FLTAPI FltSetStreamContext( + PFLT_INSTANCE Instance, + PFILE_OBJECT FileObject, + FLT_SET_CONTEXT_OPERATION Operation, + PFLT_CONTEXT NewContext, + PFLT_CONTEXT *OldContext +); + +NTSTATUS FLTAPI FltGetStreamContext( + PFLT_INSTANCE Instance, + PFILE_OBJECT FileObject, + PFLT_CONTEXT *Context +); +``` + +> `FltGetStreamContext` **increments the reference count** on the context that the Context parameter points to. When this context pointer is no longer needed, the caller must decrement its reference count by calling FltReleaseContext. **Thus every successful call to FltGetStreamContext must be matched by a subsequent call to FltReleaseContext**. + +## 设备相关 + +## 内存分配 + +### 内存分配和释放 + +分配: + +```C++ +PVOID ExAllocatePool( + __drv_strictTypeMatch(__drv_typeExpr)POOL_TYPE PoolType, + SIZE_T NumberOfBytes +); + +PVOID ExAllocatePoolWithTag( + __drv_strictTypeMatch(__drv_typeExpr)POOL_TYPE PoolType, + SIZE_T NumberOfBytes, + ULONG Tag +); + +PVOID ExAllocatePoolWithQuota( + __drv_strictTypeMatch(__drv_typeExpr)POOL_TYPE PoolType, + SIZE_T NumberOfBytes +); + +PVOID ExAllocatePoolWithQuotaTag( + __drv_strictTypeMatch(__drv_typeExpr)POOL_TYPE PoolType, + SIZE_T NumberOfBytes, + ULONG Tag +); +``` + +释放: + +```C++ +void ExFreePool( + a +); + +void ExFreePoolWithTag( + PVOID P, + ULONG Tag +); +``` + +### Lookaside结构 + +初始化: + +```C++ +void ExInitializeNPagedLookasideList( + PNPAGED_LOOKASIDE_LIST Lookaside, + PALLOCATE_FUNCTION Allocate, + PFREE_FUNCTION Free, + ULONG Flags, + SIZE_T Size, + ULONG Tag, + USHORT Depth +); + +void ExInitializePagedLookasideList( + PPAGED_LOOKASIDE_LIST Lookaside, + PALLOCATE_FUNCTION Allocate, + PFREE_FUNCTION Free, + ULONG Flags, + SIZE_T Size, + ULONG Tag, + USHORT Depth +); +``` + +申请内存: + +```C++ +PVOID ExAllocateFromNPagedLookasideList( + PNPAGED_LOOKASIDE_LIST Lookaside +); + +PVOID ExAllocateFromPagedLookasideList( + PPAGED_LOOKASIDE_LIST Lookaside +); +``` + +释放内存: + +```C++ +void ExFreeToNPagedLookasideList( + PNPAGED_LOOKASIDE_LIST Lookaside, + PVOID Entry +); + +void ExFreeToPagedLookasideList( + PPAGED_LOOKASIDE_LIST Lookaside, + PVOID Entry +); +``` + +删除Lookaside + +```C++ +void ExDeleteNPagedLookasideList( + PNPAGED_LOOKASIDE_LIST Lookaside +); + +void ExDeletePagedLookasideList( + PPAGED_LOOKASIDE_LIST Lookaside +); +``` + +### 内存复制 + +### 内存比较 + +### 内存填充 + +## 进程相关 + +## 断言 + +```C++ +ASSERT(str!=NULL);//断言 +``` + +> 如果其中的条件失败则触发断言,引起一下异常。 + +``` + +`php/Composer教程.md`: + +```md +# Composer教程 + +[教程来源](https://docs.phpcomposer.com/) + +## 介绍 + +composer是PHP的一个依赖管理工具。它允许你申明项目所依赖的代码库,它会在你的项目中为你安装它们。 + + + +简单来说,Composer 是一个新的安装包管理工具,服务于 PHP 生态系统。它实际上包含了两个部分:[Composer](https://getcomposer.org/) 和 [Packagist](https://packagist.org/)。 + + + +### Composer + +Composer 是由 Jordi Boggiano 和 Nils Aderman 创造的一个命令行工具,它的使命就是帮你为项目自动安装所依赖的开发包。Composer 中的很多理念都借鉴自 [npm](https://www.npmjs.com/) 和 [Bundler](http://bundler.io/),如果你对这两个工具有所了解的话,就会在 composer 中发现他们的身影。Composer 包含了一个依赖解析器,用来处理开发包之间复杂的依赖关系;另外,它还包含了下载器、安装器等有趣的东西。 + +作为一个用户,你所要做的就是在 `composer.json` 文件中声明当前项目所依赖的开发包,然后运行 `composer.phar install` 就行了。**`composer.json` 文件定义了当前项目所依赖的开发包和 composer 的配置信息。**下面是一个小型实例: + +```json +{ + "require": { + "monolog/monolog": "1.2.*" + } +} +``` + +接下来只要运行以下命令即可安装依赖包: + +```shell +composer install +``` + +接下来就开始下载`1.2.*`版本的代码 + + + + + +### Packagist + +Packagist 是 Composer 的默认的开发包仓库。你可以将自己的安装包提交到 packagist,将来你在自己的 VCS (源码管理软件,比如 Github)仓库中新建了 tag 或更新了代码,packagist 都会自动构建一个新的开发包。这就是 packagist 目前的运作方式,将来 packagist 将允许直接上传开发包。 + + + +### 依赖管理 + +Composer 不是一个包管理器。是的,它涉及 "packages" 和 "libraries",但它在每个项目的基础上进行管理,在你项目的某个目录中(例如 `vendor`)进行安装。默认情况下它不会在全局安装任何东西。因此,这仅仅是一个依赖管理。 + +Composer 将这样为你解决问题: + +a) 你有一个项目依赖于若干个库。 + +b) 其中一些库依赖于其他库。 + +c) 你声明你所依赖的东西。 + +d) Composer 会找出哪个版本的包需要安装,并安装它们(将它们下载到你的项目中)。 + + + +### 声明依赖关系 + +比方说,你正在创建一个项目,你需要一个库来做日志记录。你决定使用 [monolog](https://github.com/Seldaek/monolog)。为了将它添加到你的项目中,你所需要做的就是创建一个 `composer.json` 文件,其中描述了项目的依赖关系。 + +```json +{ + "require": { + "monolog/monolog": "1.2.*" + } +} +``` + +我们只要指出我们的项目需要一些 `monolog/monolog` 的包,从 `1.2` 开始的任何版本。 + + + +### 系统要求 + +运行 Composer 需要 PHP 5.3.2+ 以上版本。一些敏感的 PHP 设置和编译标志也是必须的,但对于任何不兼容项安装程序都会抛出警告。 + +我们将从包的来源直接安装,而不是简单的下载 zip 文件,你需要 git 、 svn 或者 hg ,这取决于你载入的包所使用的版本管理系统。 + +Composer 是多平台的,我们努力使它在 Windows 、 Linux 以及 OSX 平台上运行的同样出色。 + + + +## 使用 Composer + +现在我们将使用 Composer 来安装项目的依赖。如果在当前目录下没有一个 `composer.json` 文件,请查看[基本用法](https://docs.phpcomposer.com/01-basic-usage.html)章节。 + +要解决和下载依赖,请执行 `install` 命令: + +```sh +php composer.phar install +``` + +如果你进行了全局安装,并且没有 phar 文件在当前目录,请使用下面的命令代替: + +```sh +composer install +``` + +继续 [上面的例子](https://docs.phpcomposer.com/00-intro.html#Declaring-dependencies),这里将下载 monolog 到 `vendor/monolog/monolog` 目录。 + + + +### require + +除了使用`install`命令外,也可以使用`require`命令快速的安装一个依赖而不需要手动在`composer.json`里添加依赖信息: + +```shell +composer require monolog/monolog +``` + + + +### update + +`update`命令用于更新项目里的所有的包,或者指定的某些包: + +```shell +# 更新所有依赖 +composer update + +# 更新指定的包 +composer update monolog/monolog + +# 更新指定的多个包 +composer update monolog/monolog symfony/dependency-injection + +# 还可以通过对通配符匹配包 +composer update monolog/monolog symfony/* +``` + + + +### remove + +`remove`命令用于移除一个包及其依赖,如果依赖被其他包使用,则无法移除: + +```shell +composer remove monolog/monolog +``` + + + +## 自动加载 + +除了库的下载,**Composer 还准备了一个自动加载文件,它可以加载 Composer 下载的库中所有的类文件。**使用它,你只需要将下面这行代码添加到你项目的引导文件中: + +```php +require 'vendor/autoload.php'; +``` + +现在我们就可以使用 monolog 了! + + + +## 基本用法 + +### `composer.json`:项目安装 + +要开始在你的项目中使用 Composer,你只需要一个 `composer.json` 文件。该文件包含了项目的依赖和其它的一些元数据。 + +这个 [JSON format](http://json.org/) 是很容易编写的。它允许你定义嵌套结构。 + + + +### 关于 `require` Key + +第一件事情(并且往往只需要做这一件事),你需要在 `composer.json` 文件中指定 `require` key 的值。你只需要简单的告诉 Composer 你的项目需要依赖哪些包。 + +```json +{ + "require": { + "monolog/monolog": "1.0.*" + } +} +``` + +你可以看到, `require` 需要一个 **包名称** (例如 `monolog/monolog`) 映射到 **包版本** (例如 `1.0.*`) 的对象。 + + + +### 包名称 + +包名称由供应商名称和其项目名称构成。通常容易产生相同的项目名称,而供应商名称的存在则很好的解决了命名冲突的问题。它允许两个不同的人创建同样名为 `json` 的库,而之后它们将被命名为 `igorw/json` 和 `seldaek/json`。 + +这里我们需要引入 `monolog/monolog`,供应商名称与项目的名称相同,对于一个具有唯一名称的项目,我们推荐这么做。它还允许以后在同一个命名空间添加更多的相关项目。如果你维护着一个库,这将使你可以很容易的把它分离成更小的部分。 + + + +### 包版本 + +在前面的例子中,我们引入的 monolog 版本指定为 `1.0.*`。这表示任何从 `1.0` 开始的开发分支,它将会匹配 `1.0.0`、`1.0.2` 或者 `1.0.20`。 + +版本约束可以用几个不同的方法来指定。 + +| 名称 | 实例 | 描述 | +| :----------- | :-------------------------------------- | :----------------------------------------------------------- | +| 确切的版本号 | `1.0.2` | 你可以指定包的确切版本。 | +| 范围 | `>=1.0` `>=1.0,<2.0` `>=1.0,<1.1|>=1.2` | 通过使用比较操作符可以指定有效的版本范围。 有效的运算符:`>`、`>=`、`<`、`<=`、`!=`。 你可以定义多个范围,用逗号隔开,这将被视为一个**逻辑AND**处理。一个管道符号`|`将作为**逻辑OR**处理。 AND 的优先级高于 OR。 | +| 通配符 | `1.0.*` | 你可以使用通配符`*`来指定一种模式。`1.0.*`与`>=1.0,<1.1`是等效的。 | +| 赋值运算符 | `~1.2` | 这对于遵循语义化版本号的项目非常有用。`~1.2`相当于`>=1.2,<2.0`。想要了解更多,请阅读下一小节。 | + + + +### 下一个重要版本(波浪号运算符) + +`~` 最好用例子来解释: `~1.2` 相当于 `>=1.2,<2.0`,而 `~1.2.3` 相当于 `>=1.2.3,<1.3`。正如你所看到的这对于遵循 [语义化版本号](http://semver.org/) 的项目最有用。一个常见的用法是标记你所依赖的最低版本,像 `~1.2` (允许1.2以上的任何版本,但不包括2.0)。由于理论上直到2.0应该都没有向后兼容性问题,所以效果很好。你还会看到它的另一种用法,使用 `~` 指定最低版本,但允许版本号的最后一位数字上升。 + +> **注意:** 虽然 `2.0-beta.1` 严格地说是早于 `2.0`,但是,根据版本约束条件, 例如 `~1.2` 却不会安装这个版本。就像前面所讲的 `~1.2` 只意味着 `.2` 部分可以改变,但是 `1.` 部分是固定的。 + + + +### 安装依赖包 + +获取定义的依赖到你的本地项目,只需要调用 `composer.phar` 运行 `install` 命令。 + +```sh +php composer.phar install +``` + +接着前面的例子,这将会找到 `monolog/monolog` 的最新版本,并将它下载到 `vendor` 目录。 **这是一个惯例把第三方的代码到一个指定的目录 `vendor`。如果是 monolog 将会创建 `vendor/monolog/monolog` 目录。** + +> **小技巧:** 如果你正在使用Git来管理你的项目, 你可能要添加 `vendor` 到你的 `.gitignore` 文件中。 你不会希望将所有的代码都添加到你的版本库中。 + +另一件事是 `install` 命令将创建一个 `composer.lock` 文件到你项目的根目录中。 + + + +### `composer.lock` - 锁文件 + +在安装依赖后,Composer 将把安装时确切的版本号列表写入 `composer.lock` 文件。这将锁定该项目的特定版本。 + +**请提交你应用程序的 `composer.lock` (包括 `composer.json`)到你的版本库中** + +这是非常重要的,因为 `install` 命令将会检查锁文件是否存在,如果存在,它将下载指定的版本(忽略 `composer.json` 文件中的定义)。 + +这意味着,任何人建立项目都将下载与指定版本完全相同的依赖。你的持续集成服务器、生产环境、你团队中的其他开发人员、每件事、每个人都使用相同的依赖,从而减轻潜在的错误对部署的影响。即使你独自开发项目,在六个月内重新安装项目时,你也可以放心的继续工作,即使从那时起你的依赖已经发布了许多新的版本。 + +如果不存在 `composer.lock` 文件,Composer 将读取 `composer.json` 并创建锁文件。 + +这意味着如果你的依赖更新了新的版本,你将不会获得任何更新。此时要更新你的依赖版本请使用 `update` 命令。这将获取最新匹配的版本(根据你的 `composer.json` 文件)并将新版本更新进锁文件。 + +```sh +php composer.phar update +``` + +如果只想安装或更新一个依赖,你可以白名单它们: + +```sh +php composer.phar update monolog/monolog [...] +``` + +> **注意:** 对于库,并不一定建议提交锁文件 请参考:[库的锁文件](https://docs.phpcomposer.com/02-libraries.html#Lock-file). + + + +### Packagist + +[packagist](https://packagist.org/) 是 Composer 的主要资源库。 一个 Composer 的库基本上是一个包的源:记录了可以得到包的地方。Packagist 的目标是成为大家使用库资源的中央存储平台。这意味着你可以 `require` 那里的任何包。 + +当你访问 [packagist website](https://packagist.org/) (packagist.org),你可以浏览和搜索资源包。 + +**任何支持 Composer 的开源项目应该发布自己的包在 packagist 上**。虽然并不一定要发布在 packagist 上来使用 Composer,但它使我们的编程生活更加轻松。 + + + +### 自动加载 + +对于库的自动加载信息,Composer 生成了一个 `vendor/autoload.php` 文件。你可以简单的引入这个文件,你会得到一个免费的自动加载支持。 + +```php +require 'vendor/autoload.php'; +``` + +这使得你可以很容易的使用第三方代码。例如:如果你的项目依赖 monolog,你就可以像这样开始使用这个类库,并且他们将被自动加载。 + +```php +$log = new Monolog\Logger('name'); +$log->pushHandler(new Monolog\Handler\StreamHandler('app.log', Monolog\Logger::WARNING)); + +$log->addWarning('Foo'); +``` + +你可以在 `composer.json` 的 `autoload` 字段中增加自己的 autoloader。 + +```json +{ + "autoload": { + "psr-4": {"Acme\\": "src/"} + } +} +``` + +Composer 将注册一个 [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader 到 `Acme` 命名空间。 + +你可以定义一个从命名空间到目录的映射。此时 `src` 会在你项目的根目录,与 `vendor` 文件夹同级。例如 `src/Foo.php` 文件应该包含 `Acme\Foo` 类。 + +添加 `autoload` 字段后,你应该再次运行 `install` 命令来生成 `vendor/autoload.php` 文件。 + +引用这个文件也将返回 autoloader 的实例,你可以将包含调用的返回值存储在变量中,并添加更多的命名空间。这对于在一个测试套件中自动加载类文件是非常有用的,例如。 + +```php +$loader = require 'vendor/autoload.php'; +$loader->add('Acme\\Test\\', __DIR__); +``` + +除了 PSR-4 自动加载,classmap 也是支持的。这允许类被自动加载,即使不符合 PSR-0 规范。详细请查看 [自动加载-参考](https://docs.phpcomposer.com/04-schema.html#autoload)。 + +> **注意:** Composer 提供了自己的 autoloader。如果你不想使用它,你可以仅仅引入 `vendor/composer/autoload_*.php` 文件,它返回一个关联数组,你可以通过这个关联数组配置自己的 autoloader。 + + + + + +## 库(资源包) + + + +### 每一个项目都是一个包 + +只要你有一个 `composer.json` 文件在目录中,那么整个目录就是一个包。当你添加一个 `require` 到项目中,你就是在创建一个依赖于其它库的包。你的项目和库之间唯一的区别是,你的项目是一个没有名字的包。 + +为了使它成为一个可安装的包,你需要给它一个名称。你可以通过 `composer.json` 中的 `name` 来定义: + +```json +{ + "name": "acme/hello-world", + "require": { + "monolog/monolog": "1.0.*" + } +} +``` + +在这种情况下项目的名称为 `acme/hello-world`,其中 `acme` 是供应商的名称。供应商的名称是必须填写的。 + +> **注意:** 如果你不知道拿什么作为供应商的名称, 那么使用你 github 上的用户名通常是不错的选择。 虽然包名不区分大小写,但惯例是使用小写字母,并用连字符作为单词的分隔。 + + + +### 指明版本 + +你需要一些方法来指明自己开发的包的版本,当你在 Packagist 上发布自己的包,它能够从 VCS (git, svn, hg) 的信息推断出包的版本,因此你不必手动指明版本号,并且也不建议这样做。请查看 [标签](https://docs.phpcomposer.com/02-libraries.html#Tags) 和 [分支](https://docs.phpcomposer.com/02-libraries.html#Branches) 来了解版本号是如何被提取的。 + +如果你想要手动创建并且真的要明确指定它,你只需要添加一个 `version` 字段: + +```json +{ + "version": "1.0.0" +} +``` + +> **注意:** 你应该尽量避免手动设置版本号,因为标签的值必须与标签名相匹配。 + + + +### 标签 + +对于每一个看起来像版本号的标签,都会相应的创建一个包的版本。它应该符合 'X.Y.Z' 或者 'vX.Y.Z' 的形式,`-patch`、`-alpha`、`-beta` 或 `-RC` 这些后缀是可选的。在后缀之后也可以再跟上一个数字。 + +下面是有效的标签名称的几个例子: + +- 1.0.0 +- v1.0.0 +- 1.10.5-RC1 +- v4.4.4beta2 +- v2.0.0-alpha +- v2.0.4-p1 + +> **注意:** 即使你的标签带有前缀 `v`, 由于在需要 `require` 一个[版本的约束](https://docs.phpcomposer.com/01-basic-usage.html#Package-Versions)时是不允许这种前缀的, 因此 `v` 将被省略(例如标签 `V1.0.0` 将创建 `1.0.0` 版本)。 + + + +### 分支 + +对于每一个分支,都会相应的创建一个包的开发版本。如果分支名看起来像一个版本号,那么将创建一个如同 `{分支名}-dev` 的包版本号。例如一个分支 `2.0` 将产生一个 `2.0.x-dev` 包版本(加入了 `.x` 是出于技术的原因,以确保它被识别为一个分支,而 `2.0.x` 的分支名称也是允许的,它同样会被转换为 `2.0.x-dev`)。如果分支名看起来不像一个版本号,它将会创建 `dev-{分支名}` 形式的版本号。例如 `master` 将产生一个 `dev-master` 的版本号。 + +下面是版本分支名称的一些示例: + +- 1.x +- 1.0 (equals 1.0.x) +- 1.1.x + +> **注意:** 当你安装一个新的版本时,将会自动从它 `source` 中拉取。 详细请查看 [`install`](https://docs.phpcomposer.com/03-cli.html#install) 命令。 + + + +### 别名 + +它表示一个包版本的别名。例如,你可以为 `dev-master` 设置别名 `1.0.x-dev`,这样就可以通过 require `1.0.x-dev` 来得到 `dev-master` 版本的包。 + +详细请查看[“别名”](https://docs.phpcomposer.com/articles/aliases.html)。 + + + +### 锁文件 + +如果你愿意,可以在你的项目中提交 `composer.lock` 文件。他将帮助你的团队始终针对同一个依赖版本进行测试。任何时候,这个锁文件都只对于你的项目产生影响。 + +如果你不想提交锁文件,并且你正在使用 Git,那么请将它添加到 `.gitignore` 文件中。 + + + +### 发布到 VCS(线上版本控制系统) + +一旦你有一个包含 `composer.json` 文件的库存储在线上版本控制系统(例如:Git),你的库就可以被 Composer 所安装。在这个例子中,我们将 `acme/hello-world` 库发布在 GitHub 上的 `github.com/username/hello-world` 中。 + +现在测试这个 `acme/hello-world` 包,我们在本地创建一个新的项目。我们将它命名为 `acme/blog`。此博客将依赖 `acme/hello-world`,而后者又依赖 `monolog/monolog`。我们可以在某处创建一个新的 `blog` 文件夹来完成它,并且需要包含 `composer.json` 文件: + +```json +{ + "name": "acme/blog", + "require": { + "acme/hello-world": "dev-master" + } +} +``` + +在这个例子中 `name` 不是必须的,因为我们并不想将它发布为一个库。在这里为 `composer.json` 文件添加描述。 + +现在我们需要告诉我们的应用,在哪里可以找到 `hello-world` 的依赖。为此我们需要在 `composer.json` 中添加 `repositories` 来源申明: + +```json +{ + "name": "acme/blog", + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/username/hello-world" + } + ], + "require": { + "acme/hello-world": "dev-master" + } +} +``` + +更多关于包的来源是如何工作的,以及还有什么其他的类型可供选择,请查看[资源库](https://docs.phpcomposer.com/05-repositories.html)。 + +这就是全部了。你现在可以使用 Composer 的 `install` 命令来安装你的依赖包了! + +**小结:** 任何含有 `composer.json` 的 `GIT`、`SVN`、`HG` 存储库,都可以通过 `require` 字段指定“包来源”和“声明依赖”来添加到你的项目中。 +``` + +`php/Flarum分析.md`: + +```md +# Flarum源码分析 + +## 安装 + +环境依赖: + +- **Apache** (with mod_rewrite enabled) or **Nginx** +- **PHP 7.3+** with the following extensions: curl, dom, gd, json, mbstring, openssl, pdo_mysql, tokenizer, zip +- **MySQL 5.6+** or **MariaDB 10.0.5+** +- **SSH (command-line) access** to run Composer + + + +安装命令: + +```shell +composer create-project flarum/flarum . +``` + + + +## 目录结构 + +```shell +[root@VM-4-16-centos www.xxx.com]# tree -L 1 +. +|-- CHANGELOG.md +|-- composer.json +|-- composer.lock +|-- config.php +|-- extend.php +|-- flarum +|-- LICENSE +|-- public +|-- README.md +|-- site.php +|-- storage +`-- vendor + +3 directories, 9 files +``` + + + +### composer.json + +`composer.json`文件内容: + +```json +{ + "name": "flarum/flarum", + "description": "Delightfully simple forum software.", + "type": "project", + "keywords": [ + "forum", + "discussion" + ], + "homepage": "https://flarum.org/", + "license": "MIT", + "authors": [ + { + "name": "Flarum", + "email": "info@flarum.org", + "homepage": "https://flarum.org/team" + } + ], + "support": { + "issues": "https://github.com/flarum/core/issues", + "source": "https://github.com/flarum/flarum", + "docs": "https://flarum.org/docs/" + }, + "require": { + "flarum-lang/chinese-simplified": "^1.0", + "flarum/approval": "*", + "flarum/bbcode": "*", + "flarum/core": "^1.0", + "flarum/emoji": "*", + "flarum/flags": "*", + "flarum/lang-english": "*", + "flarum/likes": "*", + "flarum/lock": "*", + "flarum/markdown": "*", + "flarum/mentions": "*", + "flarum/nicknames": "*", + "flarum/pusher": "*", + "flarum/statistics": "*", + "flarum/sticky": "*", + "flarum/subscriptions": "*", + "flarum/suspend": "*", + "flarum/tags": "*" + }, + "config": { + "preferred-install": "dist", + "sort-packages": true + } +} +``` + + + +所有的安装及依赖均存放在`vendor`目录下。 +``` + +`php/php调试环境搭建.md`: + +```md +# php 调试环境搭建 + +## 下载xdebug源码编译 + +``` +tar -xzvf xdebug.tgz + +cd xdebug + +/usr/local/php/bin/phpize + +./configure --enable-xdebug --with-php-config=/usr/local/php/bin/php-config + +make && make install +``` + +## 设置php.ini + +``` +zend_extension = /yourpath/xdebug.so +xdebug.remote_enable = 1 +xdebug.remote_autostart= 1 +``` + +## 设置VSCode调试环境 + +``` +"php.executablePath": "/usr/bin/php" +``` + + + +## PHPStudy设置 + +`php.ini`设置`xdebug`插件并修改 + +```shell +xdebug.profiler_enable=On +xdebug.remote_enable=On +xdebug.remote_autostart=On +``` +``` + +`php/使用PHPStudy和PHPStorm搭建调试环境.md`: + +```md +## PHPStudy 中 php.ini 配置 + +``` +zend_extension = "D:\Program Files\phpstudy_pro\Extensions\php\php7.3.4nts\ext\php_xdebug.dll" +xdebug.remote_autostart = 1 +xdebug.remote_enable = On +xdebug.remote_host = 127.0.0.1 +xdebug.remote_port = 9100 +xdebug.idekey = PHPSTORM +``` + + +## PHPStorm设置 + +设置调试配置: + +![images](./images/1587521835(1).jpg) + + +![images](./images/1587521909(1).jpg) + + +![images](./images/1587521962(1).jpg) + + +配置调试所用的选项: + +![images](./images/1587522010(1).jpg) + +![images](./images/1587522056(1).jpg) + + +电话状态为处于监听状态,此时按下虫子就可以开始调试了。 + +![images](./images/1587522099(1).jpg) +``` + +`xposed/BSTweaker使用方法.md`: + +```md +## Root BlueStacks + +ROOT时,需`Force Kill BS`。点击root标签的Unlock。 + +![aa](./images/1561726620(1).jpg) + +然后开启BS,依次点击Patch、install supersu、update su binary。 + + +## 关闭开机Google验证 + +![bb](./images/1561726712(1).jpg) + +关闭BS,点击Login with Google按钮关闭或开启。然后再开机。 + +## 安装Xposed + +开机状态下安装xposed install安装包。 + +![cc](./images/1561726901(1).jpg) + +点击xposed Patch按钮。成功后,打开xposed install app安装 xposed 框架。 + +``` + +`xposed/Root BlueStacks.md`: + +```md +## 工具 + +下载`BSTweaker` + +``` + +`密码学/分组密码.md`: + +```md +# 分组密码 + +密码学中,分组密码的**工作模式**(mode of operation)允许使用同**一个分组密码密钥**对**多于一块**的数据进行加密,并保证其安全性。**分组密码自身只能加密长度等于密码分组长度的单块数据**,若要加密变长数据,则数据必须先被划分为一些单独的密码块。通常而言,最后一块数据也需要使用合适填充方式将数据扩展到符合密码块大小的长度。一种工作模式描述了加密每一数据块的过程,并常常使用基于一个通常称为初始化向量的附加输入值以进行随机化,以保证安全。 + + + +## 电子密码本(ECB) + +最简单的加密模式即为**电子密码本**(Electronic codebook,ECB)模式。需要加密的消息按照块密码的块大小被分为数个块,并对每个块进行独立加密。 + +[![Ecb encryption.png](https://raw.githubusercontent.com/supermanc88/ImageSources/master/Ecb_encryption.png)](https://zh.wikipedia.org/wiki/File:Ecb_encryption.png) + +[![Ecb decryption.png](https://raw.githubusercontent.com/supermanc88/ImageSources/master/Ecb_decryption.png)](https://zh.wikipedia.org/wiki/File:Ecb_decryption.png) + +本方法的缺点在于同样的明文块会被加密成相同的密文块;因此,它不能很好的隐藏数据模式。在某些场合,这种方法不能提供严格的数据保密性,因此并不推荐用于密码协议中。下面的例子显示了ECB在密文中显示明文的模式的程度:该图像的一个位图版本(左图)通过ECB模式可能会被加密成中图,而非ECB模式通常会将其加密成右图。 + +| [![Tux.jpg](https://raw.githubusercontent.com/supermanc88/ImageSources/master/Tux.jpg)](https://zh.wikipedia.org/wiki/File:Tux.jpg) | [![Tux ecb.jpg](https://raw.githubusercontent.com/supermanc88/ImageSources/master/Tux_ecb.jpg)](https://zh.wikipedia.org/wiki/File:Tux_ecb.jpg) | [![Tux secure.jpg](https://raw.githubusercontent.com/supermanc88/ImageSources/master/Tux_secure.jpg)](https://zh.wikipedia.org/wiki/File:Tux_secure.jpg) | +| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | +| 原图 | 使用ECB模式加密 | 提供了伪随机性的非ECB模式 | + +右图是使用CBC,CTR或任何其它的更安全的模式加密左图可能产生的结果——与随机噪声无异。注意右图看起来的随机性并不能表示图像已经被安全的加密;许多不安全的加密法也可能产生这种“随机的”输出。 + +ECB模式也会导致使用它的协议不能提供数据完整性保护,易受到[重放攻击](https://zh.wikipedia.org/wiki/重放攻击)的影响,因此每个块是以完全相同的方式解密的。 + + + +## 密码块链接(CBC) + +1976年,IBM发明了**密码分组链接**(CBC,Cipher-block chaining)模式。在CBC模式中,每个明文块先与前一个密文块进行异或后,再进行加密。在这种方法中,每个密文块都依赖于它前面的所有明文块。同时,为了保证每条消息的唯一性,在第一个块中需要使用初始化向量。 + +[![Cbc encryption.png](https://raw.githubusercontent.com/supermanc88/ImageSources/master/Cbc_encryption.png)](https://zh.wikipedia.org/wiki/File:Cbc_encryption.png) + +[![Cbc decryption.png](https://raw.githubusercontent.com/supermanc88/ImageSources/master/Cbc_decryption.png)](https://zh.wikipedia.org/wiki/File:Cbc_decryption.png) + + + +CBC是最为常用的工作模式。它的主要缺点在于加密过程是串行的,无法被并行化,而且消息必须被填充到块大小的整数倍。解决后一个问题的一种方法是利用密文窃取。 + +注意在加密时,明文中的微小改变会导致其后的全部密文块发生改变,而在解密时,从两个邻接的密文块中即可得到一个明文块。因此,解密过程可以被并行化,而解密时,密文中一位的改变只会导致其对应的明文块完全改变和下一个明文块中对应位发生改变,不会影响到其它明文的内容。 +``` + +`密码学/对称加密算法.md`: + +```md +# 对称加密算法 + + + +## 填充方式 + +提供明文和密钥,将明文按照64bit分块(对应8个字节),不足8个字节的可以进行填充(填充方式多种),密钥必须为8个字节共64bit + +当明文长度不为分组长度的整数倍时,需要在最后一个分组中填充一些数据使其凑满一个分组长度。 + + * NoPadding + API或算法本身不对数据进行处理,加密数据由加密双方约定填补算法。例如若对字符串数据进行加解密,可以补充\0或者空格,然后trim + + * PKCS5Padding + 加密前:数据字节长度对8取余,余数为m,若m>0,则补足8-m个字节,字节数值为8-m,即差几个字节就补几个字节,字节数值即为补充的字节数若为0则补充8个字节的8 + 解密后:取最后一个字节,值为m,则从数据尾部删除m个字节,剩余数据即为加密前的原文。 + 例如:加密字符串为为AAA,则补位为AAA55555;加密字符串为BBBBBB,则补位为BBBBBB22;加密字符串为CCCCCCCC,则补位为CCCCCCCC88888888。 + + * PKCS7Padding + PKCS7Padding 的填充方式和PKCS5Padding 填充方式一样。只是加密块的字节数不同。PKCS5Padding明确定义了加密块是8字节,PKCS7Padding加密块可以是1-255之间。 + + + + + +[对称加密算法:DES、3DES - 简书 (jianshu.com)](https://www.jianshu.com/p/ebcce1beafaf) +``` + +`密码学/数字签名.md`: + +```md +**数字签名是什么?** + + + +1. + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/bg2011080901.png) + +鲍勃有两把钥匙,一把是公钥,另一把是私钥。 + +2. + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/bg2011080902.png) + +鲍勃把公钥送给他的朋友们----帕蒂、道格、苏珊----每人一把。 + +3. + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/bg2011080903.png) + +苏珊要给鲍勃写一封保密的信。她写完后用鲍勃的公钥加密,就可以达到保密的效果。 + +4. + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/bg2011080904.png) + +鲍勃收信后,用私钥解密,就看到了信件内容。这里要强调的是,只要鲍勃的私钥不泄露,这封信就是安全的,即使落在别人手里,也无法解密。 + +5. + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/bg2011080905.png) + +鲍勃给苏珊回信,决定采用"数字签名"。他写完后先用Hash函数,生成信件的摘要(digest)。 + +6. + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/bg2011080906.png) + +然后,鲍勃使用私钥,对这个摘要加密,生成"数字签名"(signature)。 + +7. + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/bg2011080907.png) + +鲍勃将这个签名,附在信件下面,一起发给苏珊。 + +8. + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/bg2011080908.png) + +苏珊收信后,取下数字签名,用鲍勃的公钥解密,得到信件的摘要。由此证明,这封信确实是鲍勃发出的。 + +9. + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/bg2011080909.png) + +苏珊再对信件本身使用Hash函数,将得到的结果,与上一步得到的摘要进行对比。如果两者一致,就证明这封信未被修改过。 + +10. + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/bg2011080910.png) + +复杂的情况出现了。道格想欺骗苏珊,他偷偷使用了苏珊的电脑,用自己的公钥换走了鲍勃的公钥。此时,苏珊实际拥有的是道格的公钥,但是还以为这是鲍勃的公钥。因此,道格就可以冒充鲍勃,用自己的私钥做成"数字签名",写信给苏珊,让苏珊用假的鲍勃公钥进行解密。 + +11. + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/bg2011080911.png) + +后来,苏珊感觉不对劲,发现自己无法确定公钥是否真的属于鲍勃。她想到了一个办法,要求鲍勃去找"证书中心"(certificate authority,简称CA),为公钥做认证。证书中心用自己的私钥,对鲍勃的公钥和一些相关信息一起加密,生成"数字证书"(Digital Certificate)。 + +12. + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/bg2011080912.png) + +鲍勃拿到数字证书以后,就可以放心了。以后再给苏珊写信,只要在签名的同时,再附上数字证书就行了。 + +13. + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/bg2011080913.png) + +苏珊收信后,用CA的公钥解开数字证书,就可以拿到鲍勃真实的公钥了,然后就能证明"数字签名"是否真的是鲍勃签的。 + +14. + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/bg2011080914.jpg) + +下面,我们看一个应用"数字证书"的实例:https协议。这个协议主要用于网页加密。 + +15. + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/bg2011080915.png) + +首先,客户端向服务器发出加密请求。 + +16. + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/bg2011080916.png) + +服务器用自己的私钥加密网页以后,连同本身的数字证书,一起发送给客户端。 + +17. + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/bg2011080917.png) + +客户端(浏览器)的"证书管理器",有"受信任的根证书颁发机构"列表。客户端会根据这张列表,查看解开数字证书的公钥是否在列表之内。 + +18. + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/bg2011080918.png) + +如果数字证书记载的网址,与你正在浏览的网址不一致,就说明这张证书可能被冒用,浏览器会发出警告。 + +19. + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/bg2011080919.jpg) + +如果这张数字证书不是由受信任的机构颁发的,浏览器会发出另一种警告。 + +20. + +![img](https://raw.githubusercontent.com/supermanc88/ImageSources/master/bg2011080920.png) + +如果数字证书是可靠的,客户端就可以使用证书中的服务器公钥,对信息进行加密,然后与服务器交换加密信息。 + +(完) +``` + +`未整理/Writing a File System Driver.md`: + +```md +p390 + +DriverEntry +文件系统驱动程序通常在其驱动程序入口例程中执行下面列出的步骤。 + +1. 为全局数据结构分配内存并初始化这些数据结构。 +2. 如果需要,请读取注册表信息。虽然大多数文件系统实现不会提供许多可配置的参数,但是重定向器和服务器(例如局域网管理器软件)确实允许用户指定许多可配置参数的值。 +3. 创建一个设备对象,可以满足针对FSD本身的请求(与针对由FSD管理的逻辑卷的请求相对)。 +- FILE_DEVICE_DISK_FILE_SYSTEM + 这由基于磁盘的FSD实现使用,例如NTFS(设备对象名为\Ntfs)和FAT(设备对象名为\Fai)。 +- FILE_DEVICE_NETWORK +- FILE_DEVICE_TAPE_FILE_SYSTEM +- FILE_DEVICE_NETWORK_FILE_SYSTEM + +4. 初始化不同IRP请求的调度例程函数指针 +5. 初始化fast IO和用于跨模块同步的回调函数 +6. 初始化您的FSD可能需要的任何计时器对象和相关联的DPC对象。一些FSD实现使用定时器中断来执行异步处理。这需要使用定时器对象。 +7. 如果需要,启动异步初始化。例如,如果您的驱动程序需要创建异步执行某些初始化的工作线程,这些线程可以在DriverEntry()例程的上下文中创建,也可以作为异步操作创建。 +8. 基于物理媒体的文件系统驱动程序将调用loRegisterFile-System()来注册驱动程序的当前加载实例。请注意,NT输入/输出管理器支持的基于物理介质的前端设备支持基于磁盘、虚拟磁盘、光盘和磁带的前端设备。通过使用输入/输出管理器对文件系统进行注册,您的文件系统将确保它在输入/输出管理器要求检查的文件系统驱动程序列表中,并在一个或多个周期内首次访问的介质上有效地执行装载操作 +9. 支持通用命名约定(UNC)名称的网络文件系统实现将调用FsRtlRegisterUncProvider()向MUP组件注册自己。 +10. 网络重定向器和服务器还将使用IoRegisterShutdownNotification()例程注册关闭通知功能 确保FSD有机会在系统停机前刷新修改后的数据,并执行任何其他必要的处理。 + +``` + +`未整理/vpb.txt`: + +```txt +p371 +卷设备对象表示装载的逻辑卷的实例。该设备对象由文件系统驱动程序实现创建; + +在访问存储设备上的文件系统数据之前,大多数操作系统通常需要装载操作,装载操作由平台上的文件系统驱动程序来执行。 +装载卷的过程只是为了让文件系统驱动程序有机会为后续访问准备卷。 + + +通常,文件系统驱动程序将首先检查磁盘上的数据结构,以确定要装载的介质是否包含有效的文件系统信息。如果这些检查通过, +文件系统将读入基本卷信息,如卷大小、根目录位置、空闲块映射、分配的群集映射等,然后创建必要的内存结构,用于支持对卷的访问。 + + +在Windows NT系统下,I/O管理器要求创建一个代表装入的逻辑卷的设备对象(卷设备对象), +对于物理介质,与代表物理介质的设备对象相关联的VPB被正确初始化。 +使用属于物理设备对象的VPB结构,每个卷设备对象在逻辑上与物理设备对象相关联。 +这种关联发生在卷装载时,这种情况发生在输入/输出管理器收到驻留在物理设备对象上的对象的第一个创建/打开请求时。(应该是这个磁盘上的一个文件被打开或创建时) + + +流程: +1.例如,对“C:\dirl”的打开 +2.输入/输出管理器获得一个指向VPB结构的指针,该结构与表示“C:”的物理设备对象相关联; +3.输入/输出管理器检查VPB中的“Flags”字段,查看物理设备对象是否存在已装入的逻辑卷; +4.如果(不存在已装入的卷,即未设置VPB _ MOUNTED) +4.1 输入/输出管理器请求每个文件系统(ntfs、fat32)检查它们是否希望在物理设备对象上执行装载 +4.2 文件系统驱动程序之一执行装载操作; +4.3 作为“装载”过程的一部分,FSD将创建FILE_DEVICE_DISK_FILE_SYSTEM类型的设备对象或网络等效对象; +4.4 现在,VPB中的DeviceObject字段将指向逻辑卷设备对象(这是由执行装载的FSD完成的); +4.5 有的文件系统为装载请求返回STATUS_SUCCESS 时,输入/输出管理器将在VPB的标志字段中设置VPB_MOUNTED标志 +5. 现在,输入/输出管理器可以继续执行与创建/打开请求相关的其他指令 + +VPB已经是关联上物理设备对象了,就是在装载的时候创建对应的卷设备对象并关联。 + + +p372 +VPB +该结构在物理磁盘设备对象和逻辑卷设备对象之间创建逻辑关联的重要任务 +VPB结构仅存在于可以装载的物理、虚拟和/或逻辑介质的设备对象中。 +因此,如果您设计网络重定向器和/或服务器,您将不会直接与VPB结构交互。 +注意,在物理设备对象和表示安装的逻辑卷设备对象之间没有物理关联 +(即,没有指针从逻辑卷设备对象直接通向物理设备对象,反之亦然,这通常发生在两个设备对象通过连接操作连接时) +对于文件系统装载操作,两个设备对象之间的唯一关联是通过VPB结构的逻辑连接。 +输入/输出管理器知道两个设备对象之间的逻辑关联,并且在收到对磁盘的创建/打开请求时, +总是检查VPB结构,以确定请求实际应该指向的文件系统卷设备对象。 + + +p373 +Also note that each file object structure representing a successful open operationon an on-clisk object points to the VPB structure belonging to the physical deviceon which the opened on-disk object resides. +每个文件对象结构表示一个指向属于打开的磁盘的VPB结构的磁盘上对象的成功打开操作(其实就是一个磁盘上文件正确的打开生成了文件对象) + +Volume control block (VCB) +作为装载过程的一部分,为了使文件系统能够有序、正确地访问逻辑卷中包含的数据。 +维护的有助于此过程的结构之一是VCB结构 + +VCB结构包含诸如指向内存中根目录结构(即根目录的FCB)的指针、逻辑卷上当前打开的文件流的数量、表示逻辑卷在任何给定时刻的状态的一些标志、 +用于保持VCB结构本身完整性的同步结构以及其他类似信息等基本信息 + +一般情况下,在创建逻辑卷设备对象时,VCB作为设备对象扩展的一部分 + + +p374 +许多Windows NT FSD的实现使用缓存管理器来缓存磁盘卷元数据结构。 这是通过创建流文件对象来表示打开的磁盘卷信息,然后启动对该文件对象的缓存来实现的。 +使用CcMapData()将数据映射到系统缓存中,并且可以轻松访问它,就像通常由用户线程访问缓存的文件流数据一样。 + + +typedef struct _SFsdVolumeControlBlock +{ +SFsdldentifier Nodeldentifier; + +//a resource to protect the fields contained within the VCB 保护VCB中包含的字段的资源 +ERESOURCE VCBResource; + +// each VCB is accessible on a global linked list 每个VCB都可以在全局链表上访问 +LIST_ENTRY NextVCB; + +// each VCB points to a VPB structure created by the NT I/O Manager 每个VCB指向由NT I / O管理器创建的VPB结构 +PVPB PtrVPB; + +// a set of flags that might mean something useful +uint32 VCBFlags; + +// A count of the number of open files/directories +//As long as the count is != 0, the volume cannot +// be dismounted or locked. 打开的文件目录数量的计数//只要该计数为!= 0时,无法卸载或锁定卷。 +uint32 VCBOpenCount; + +// we will maintain a global list of IRPs that are pending +// because of a directory notify request. 我们将维护一个由于目录通知请求而挂起的IRp的全局列表 +LIST_ENTRY NextNotifyIRP; + +// the above list is protected only by the mutex declared below +KMUTEX NotifylRPMutex; + +// for each mounted volume, we create a device object. Here then +// is a back pointer to that device object 对于每个装载的卷,我们创建一个设备对象。这里//是指向该设备对象的反向指针 +PDEVICE_OBJECT VCBDeviceObject; + +//We also retain a pointer to the physical device object, which we +// have mounted ourselves. The I/O Manager passes us a pointer to this +// device object when requesting a mount operation. 我们还保留了一个指向物理设备对象的指针,我们自己安装了这个对象。 请求挂载操作时,I / O管理器向我们传递指向此//设备对象的指针。 +PDEVICE_OBJECT TargetDeviceObject; + +// the volume structure contains a pointer to the root directory FCB 卷结构包含一个指向根目录FCB的指针 +PtrSFsdFCB PtrRootDirectoryFCB; + +// For volume open operations, we do not create a FCB (we use the VCB +// directly instead). Therefore, all CCB structures for the volume +// open operation are linked directly to the VCB 对于批量打开操作,我们不创建FCB(我们直接使用VCB)。 因此,卷打开操作的所有CCB结构都直接链接到VCB +LIST_ENTRY VolumeOpenListHead; + +// Pointer to a stream file object created for the volume information +// to be more easily read from secondary storage (with the support of +// the NT Cache Manager). 指向为卷信息创建的流文件对象的指针,以便更容易从辅助存储中读取(在NT缓存管理器的支持下)。 +PFILE_OBJECT PtrStreamFileObject; + +// Required to use the Cache Manager. +SECTION_OBJECT_POINTERS SectionObject; + +// File sizes required to use the Cache Manager. +LARGE_INTEGER AllocationSize; +LARGE_INTEGER FileSize; +LARGE_INTEGER ValidDataLength; +} SFsdVCB, *PtrSFsdVCB; + +p375 + +传统文件系统通常操作两种类型的磁盘对象:文件和目录。 +据我们所知,一个文件仅仅代表存储在磁盘上的字节流。 +目录是文件系统定义的结构,包含文件信息;即目录本身没有用,仅作为定位文件的手段,而文件又包含用户的数据。 + +文件只是目录中包含的命名对象。 + +很多不太懂 + +p376 +在大多数UNIX实现中,文件或目录的内存抽象通常称为vnode。在Windows NT系统上,它被称为文件控制块。 +不管用什么术语来表示这种表示,重要的是要注意的是,一个磁盘上的对象必须总是用一个,而且只有一个,FCB来表示。 +因此,即使您的文件系统支持多个链接的文件流,您也应该只创建一个FCB来在内存中表示该文件流, +而不用考虑两个不同的进程可能使用不同的路径名或标识符来打开同一个磁盘上的对象。 + + +p378 +关于FCB结构,您应该注意的第一件事是操作系统不能确定这个结构的内容。 +因此,每个文件系统实现对FCB结构中包含的字段具有完全的灵活性。 +同样,定义的大多数fcb结构将维护一个表示fcb当前引用计数的字段,以及另一个表示fcb当前openhandlecount的字段。 + + +p379 + +虽然Windows NT允许FSD在如何定义自己的FCB结构方面具有相当大的自由度, +但是与缓存管理器和虚拟内存管理器的成功集成要求某些NT定义的结构也与FCB相关联。 +如果FSD需要使用NT系统缓存并且还支持内存映射文件,则必须与NT CacheManager和VMM集成。 + +作为成功集成的先决条件,有四个字段必须与每个FCB相关联: +1. 一个FSRTL_COMMON_FCB_HEADER类型的单个结构 +2. 一个SECTION_OBJECT_POINTERS类型的单个结构 +3. 两个ERESOURCE类型的同步结构(由示例FSD命名为主资源和分页资源) + + +p380 +永远不能从分页池中分配ERESOURCE类型对象,也不应该从pagedmemory分配CommonFCBHeader或SectionObject字段。(也就是说上面的四个结构所用的内存不能是非分页的) + +内存中的fcb结构何时创建和释放? +当首次打开字节流并且系统内存中当前不存在表示此字节流的其他FCB结构时 +例如,如果应用程序决定自系统启动以来第一次打开文件foo,则管理foo所在的逻辑卷的FSD会创建一个FCB结构以响应调用者的打开请求(即,处理 IRP_MJ_CREATE请求)。 +然而,对文件foo的后续请求将不会导致创建新的内存中FCB结构,只要先前创建的FCB结构仍然由FSD保留在存储器中。 +确定FCB是否仍由FSD保留的因素是由FCB结构中的FSD维护的ReferenceCount字段(或其等价)的值。 + +如果在接收到新的打开请求时,FSD已经将文件foo的FCB废弃,则FSD将创建新的FCB结构以在内存中表示文件foo。 + +这个文件控制块结构作为系统内存中可选字节流的唯一表示。只要任何NT组件维护FCB,就保留FCB。 +ReferenceCount值为零意味着可以安全地解除分配FCB,因为此时系统中没有组件正在主动访问与由FCB表示的字节流相关联的字节流。 + + +p381 + +ReferenceCount字段和OpenHandleCount字段。 了解这两个领域的重要性至关重要。 + +引用计数和打开句柄计数都是由fsd在fcb结构中维护的内部计数, +因此对于任何其他内核模式或用户模式组件都是不可见的。这两个计数都有助于确定何时FSD可以安全地解除分配FCB结构。 + +引用计数只是一个数字,表示文件系统驱动程序已知的此FCB结构的完整引用总数。 +只要引用计数不为零,FSD就知道某个组件正在使用FCB,因此FCB结构的内存不能被释放。 +每当FSD响应IRP_MJ_CREATE请求成功的创建/打开操作时,引用计数字段递增1。 +每当为FCB处理关闭操作以响应IRP_MJ_CLOSE请求时,该字段的内容减1。 +如果某些外部组件在FSD不知情的情况下将指针存储到FCB,则与FCB相关联的打开计数将不会增加,因此FSD无法保证一旦引用计数为0就将其保留。 + +打开句柄计数仅仅表示FCB的未完成的用户打开句柄的数量。 +该字段也作为处理IRP_MJ_CREATE请求的一部分递增; 但是,它仅在响应发给FSD的IRP_MJ_CLEAN请求时递减。 +每当用户进程最后关闭文件句柄时(即,当系统打开文件对象的代表所有用户打开实例的句柄数为0时),I / O管理器向FSD发出IRP_MJ_CLEAN请求。 + + +p382 + +FileObject结构,由NT对象管理器维护。对于文件对象结构,对象管理器维护两个计数, +一个是 每个具有一个或多个与文件对象相关联的打开句柄的进程的进程句柄计数 +以及与文件对象相关联的所有进程句柄计数的总和 + +除了这两个句柄计数值之外,对象管理器还维护所有对象的对象引用计数。 +每当ProcessHandleCount或SystemHandleCount值递增时,此引用计数总是递增。 +但是,对象引用计数可能会增加,即使进程句柄计数和系统句柄计数都不会增加(例如,内核模式组件引用对象,但不请求对象的新句柄)。比如ObreferenceXXX函数 + +当一个进程关闭一个打开的句柄时(即,当调用ZwClose()或NtClose()时),NT对象管理器会减少该对象的进程句柄计数和系统句柄计数。 +然后调用与关闭对象相关联的任何对象关闭方法;输入/对象管理器提供被称为lopCloseFile()的close例程。 +对象管理器将ProcessHandleCount和SystemHandleCount值提供给lopCloseFile()函数。 + +如果且仅当文件对象的所有未完成的用户句柄都已关闭,并且文件对象的传入系统句柄计数等于1, +则“lopCloseFile”函数向FSD发出IRP _ MJ _ CLEAN请求,这意味着在调用NtClose()操作时,文件对象上只有一个未完成的引用。 + +一旦lopCloseFile()完成处理(即FSD已经完成清理请求的处理),对象管理器将减少文件对象结构的对象引用计数。如果此引用计数值为0, +对象管理器将删除该对象,并且在此之前,调用与文件对象相关联的删除方法(lopDeleteFile()),lopDeleteFile()进而向管理文件对象结构的FSD发出IRP_MJ_CLOSE请求。 + + +p383 + +FCB有多少用户句柄未完成? +换句话说,FSD应该对成功的IRP_MJ_CREATE请求的总数有所了解,并且尚未收到相应的IRP__MJ_CLEANUP。 +只要此数字非零,FSD就知道至少有一个线程对FCB表示的文件流有一个有效的开放句柄,并且FCB结构应该保留在内存中。 + +您还应该注意到,虽然FCB的OpenHandleCount是响应于创建操作而创建的(当输入/输出管理器创建新的文件对象时), +但是该计数不一定对应于相应文件对象上的系统范围句柄计数 +该计数主要由nt对象管理器获得。每当用户文件句柄被复制(比如在同一个进程的线程之间),或者被继承(由一个进程的子进程),或者每当一些进程从文件对象指针请求一个新句柄时,对象管理器在代表打开文件实例的文件对象上增加系统句柄计数。 +由于文件句柄的这种复制或继承发生时,不会通知FSD,因此在这种情况下,FSD内部维护的OpenHandleCount不会增加。 +但是,这不会对文件系统接口造成任何问题,因为除非所有对该文件对象有打开句柄的用户线程也调用了关闭操作,否则NT输入/输出管理器不会对该文件对象调用IRP_MJ_CLEANUP请求。 +因此,FSD将始终看到对应于一个创建/打开请求的一个清理操作,并将根据清理请求减少打开句柄计数。 + +FCB结构有多少未完成的引用? +ReferenceCount 字段有助于确定FCB的未结引用总数。 +在OpenHandleCounthas降到零后很长时间内,引用计数将是非零,这是完全可能的,也是非常可能的。 +这仅仅意味着,尽管FCB的所有用户句柄都已关闭,但一些内核模式组件希望将FCB保留在内存中。 + +p384 + +通常,当NT缓存管理器和NT VMM一起合谋将文件数据缓存在内存中时会出现这种情况,即使用户应用程序进程已经关闭文件, +表明它已经完成了文件流的处理。缓存管理器和/或VMM希望在内存中保留文件数据 +(记住,除非FCB也存在,否则它们不能在内存中保留文件数据)的原因是, +如果用户应用程序需要再次访问文件流的内容,它们能够提供相对快速的响应。这对你来说可能有点傻, +但是,应用程序进程经常在几分钟内多次打开和关闭同一个文件,并将文件内容保留在内存中, +以帮助加速对同一文件流数据的第二次和后续访问,从而提高系统吞吐量。 + +总而言之,VMM或任何组件如何确保FSD在内存中保留FCB? +答案是这样的:例如,如果VMM希望确保FCB留下来,它会引用一些与FCB相关的文件对象。 +通过引用文件对象,VMM防止对象管理器对该文件对象发出关闭请求,即使该特定文件对象的所有用户句柄都已关闭。 +因此,FCB参考计数不会递减到0,而FCB会保留在内存中。 + + +Context control blocks (CCB) +FSD使用CCB结构来存储对文件流执行的特定打开操作的状态信息。 +如前所述,每个文件流在内存中都用FCB结构表示。然而, +FCB结构只包含有助于管理用户对整个文件流的访问的信息;它不包含任何关于特定用户打开操作的信息。CCB结构被用于此目的。 + +**对于文件流上的每次成功打开操作,FSD都会创建一个CCB结构。**每个CCB结构通常以某种方式与代表文件流的唯一FCB结构相关联; + +typedef struct _SFsdContextControlBlock { +SFsdldentifier Nodeldentifier; + +// Pointer to the associated FCB +struct _SFsdFileControlBlock *PtrFCB; + +// all CCB structures for a FCB are linked together +LIST_ENTRY NextCCB; + +// each CCB is associated with a file object +PFILE_OBJECT PtrFileObject; + +// flags (see below) associated with this CCB +uint32 CCBFlags; + +// current byte offset is required sometimes +LARGE_INTEGER CurrentByteOffset; + +// if this CCB represents a directory object open, we may +// need to maintain a search pattern +PSTRING DirectorySearchPattern; + +// we must maintain user specified file time values +uint32 UserSpecifiedTime; +} SFsdCCB, *PtrSFsdCCB; + +图9-3显示了三个CCB结构,每个结构都是由一个FSD响应一个IRP_MJ_CREATE请求而创建的。 +两个CCB结构用于同一个文件流(文件#1),因此它们在FCB #1上链接在一起。 +另一个CCB结构代表了在FCB #2成功打开的一个例子 + +请注意,在由输入/输出管理器响应打开/创建请求创建的文件对象结构和由FSD创建的CCB结构之间存在一对一的映射。因此,对于一个打开的文件流,只有一个FCB结构被创建,但是对于同一个文件流,可能有许多CCB结构被创建,每一个都作为文件流上成功打开操作的FSD上下文。 + +为什么文件系统希望创建一个表示每次成功打开操作的CCB结构? +CCB可用于维护有关使用特定文件对象的线程请求的字节范围锁的信息;如果线程关闭文件句柄而不解锁该句柄的所有未完成的字节范围锁,则FSD可以自动在收到文件对象上的IRP_MJ_CLEANUP清理请求后,通过检查与之关联的CCB上的未完成锁来形成解锁操作。同样地,FSD也经常使用CCB来存储有关下一个偏移量的信息,从中恢复目录搜索操作,以便首先查找并查找应用程序发出的下一个请求。 + +p386 +FILE OBJECT +文件对象结构中的每一个都由I / O管理器分配和维护 +文件系统驱动程序负责初始化文件对象结构中的FsContext和FsContext2字段。在图9-3中,您将看到FsContext2字段似乎指向CCB结构。如前所述,打开操作的每个实例都由CCB结构表示,为了能够正确地将文件对象与相应的CCB相关联,在Windows下的大多数文件系统实现都将FsContext2字段初始化为处理IRP_MJ_CREATE请求的一部分,以引用打开操作期间新分配的CCB结构。请注意,这种类型的关联不是由NT IO管理器强制要求的。但是,如果您确实开发了一个过滤器驱动程序,它将自己附加到一个设备对象上,该对象代表一个本地NT文件系统(例如FASTFAT、NTFS或CDFS)的已装载逻辑卷,那么您应该期望文件系统实现已经初始化了FsContext2字段,以引用FSD内部的CCB结构 + +您的驱动程序必须初始化FsContext字段,以指向与FCB相关联的通用FCB报头结构。该初始化是由FSD执行的,作为处理IRP_MJ_CREATE请求的一部分。 +Fcb->CoFcb = (PFSRTL_COMMON_FCB_HEADER)rFileObject->FsContext; + +其他的数据结构 +支持 byte-range locking +不懂 +p387 +Support for a Dynamic Name Lookup Cache (DNLC) implementation + +p390 + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +`科普/SSD为什么要4K对齐.md`: + +```md +# SSD为什么要4K对齐 + + + +在对磁盘进行分区时,有一个很重要的注意事项,就是要将分区对齐,不对齐可能会造成磁盘性能的下降。尤其是固态硬盘SSD,基本上都要求4K对齐。磁盘读写速度慢还找不到原因?可能就是4K对齐的锅。那么分区对齐究竟是怎么回事?为什么要对齐?如何才能对齐?如何检测是否对齐呢?今天,我们就来说说分区4K对齐这些事。你想知道的都在这里了。 + +1. [物理扇区的概念](https://www.diskgenius.cn/exp/about-4k-alignment.php#tit-1) +2. [分区及其格式化](https://www.diskgenius.cn/exp/about-4k-alignment.php#tit-2) +3. [为什么要分区对齐](https://www.diskgenius.cn/exp/about-4k-alignment.php#tit-3) +4. 如何才能对齐 + 1. [如何检测物理扇区大小](https://www.diskgenius.cn/exp/about-4k-alignment.php#tit-4-1) + 2. [对齐到多少个扇区才正确](https://www.diskgenius.cn/exp/about-4k-alignment.php#tit-4-2) + 3. [为什么大家都说4K对齐](https://www.diskgenius.cn/exp/about-4k-alignment.php#tit-4-3) + 4. [划分分区时如何具体操作分区对齐](https://www.diskgenius.cn/exp/about-4k-alignment.php#tit-4-4) +5. [如何检测是否对齐](https://www.diskgenius.cn/exp/about-4k-alignment.php#tit-5) +6. [如何纠正未对齐的分区](https://www.diskgenius.cn/exp/about-4k-alignment.php#tit-6) + + + +## 物理扇区的概念 + +分区对齐,是指将分区起始位置对齐到一定的扇区。我们要先了解对齐和扇区的关系。我们知道,硬盘的基本读写单位是“扇区”。对于硬盘的读写操作,每次读写都是以扇区为单位进行的,最少一个扇区,通常是512个字节。由于硬盘数据存储结构的限制,单独读写1个或几个字节是不可能的。通过系统提供的接口读写文件数据时,看起来可以单独读写少量字节,实际上是经过了操作系统的转换才实现的。硬盘实际执行时读写的仍然是整个扇区。 + +近年来,随着对硬盘容量的要求不断增加,为了提高数据记录密度,硬盘厂商往往采用增大扇区大小的方法,于是出现了扇区大小为4096字节的硬盘。我们将这样的扇区称之为“物理扇区”。但是这样的大扇区会有兼容性问题,有的系统或软件无法适应。为了解决这个问题,硬盘内部将物理扇区在逻辑上划分为多个扇区片段并将其作为普通的扇区(一般为512字节大小)报告给操作系统及应用软件。这样的扇区片段我们称之为“逻辑扇区”。实际读写时由硬盘内的程序(固件)负责在逻辑扇区与物理扇区之间进行转换,上层程序“感觉”不到物理扇区的存在。 + +逻辑扇区是硬盘可以接受读写指令的最小操作单元,是操作系统及应用程序可以访问的扇区,多数情况下其大小为512字节。我们通常所说的扇区一般就是指的逻辑扇区。物理扇区是硬盘底层硬件意义上的扇区,是实际执行读写操作的最小单元。是只能由硬盘直接访问的扇区,操作系统及应用程序一般无法直接访问物理扇区。一个物理扇区可以包含一个或多个逻辑扇区(比如多数硬盘的物理扇区包含了8个逻辑扇区)。当要读写某个逻辑扇区时,硬盘底层在实际操作时都会读写逻辑扇区所在的整个物理扇区。 + +这里说的“硬盘”及其“扇区”的概念,同样适用于存储卡、固态硬盘(SSD)。接下来我们统称其为“磁盘”。它们在使用上的基本原理是一致的。其中固态硬盘在实现上更加复杂,它有“页”和“块”的概念,为了便于理解,我们可以简单的将其视同为逻辑扇区和物理扇区。另外固态硬盘在写入数据之前必须先执行擦除操作,不能直接写入到已存有数据的块,必须先擦除再写入。所以固态硬盘(SSD)对分区4K对齐的要求更高。如果没有对齐,额外的动作会增加更多,造成读写性能下降。 + +## 分区及其格式化 + +磁盘在使用之前必须要先分区并格式化。简单的理解,分区就是指从磁盘上划分出来的一大片连续的扇区。格式化则是对分区范围内扇区的使用进行规划。比如文件数据的储存如何安排、文件属性储存在哪里、目录结构如何存储等等。分区经过格式化后,就可以存储文件了。格式化程序会将分区里面的所有扇区从头至尾进行分组,划分为固定大小的“簇”,并按顺序进行编号。每个“簇”可固定包含一个或多个扇区,其扇区个数总是2的n次方。格式化以后,分区就会以“簇”为最小单位进行读写。文件的数据、属性等等信息都要保存到“簇”里面。 + +## 为什么要分区对齐 + +为磁盘划分分区时,是以逻辑扇区为单位进行划分的,分区可以从任意编号的逻辑扇区开始。如果分区的起始位置没有对齐到某个物理扇区的边缘,格式化后,所有的“簇”也将无法对齐到物理扇区的边缘。如下图所示,每个物理扇区由4个逻辑扇区组成。分区是从3号扇区开始的。格式化后,每个簇占用了4个扇区,这些簇都没有对齐到物理扇区的边缘,也就是说,每个簇都跨越了2个物理扇区。 + +![为什么要分区对齐](https://raw.githubusercontent.com/supermanc88/ImageSources/master/4k-align-01.png) + +由于磁盘总是以物理扇区为单位进行读写,在这样的分区情况下,当要读取某个簇时,实际上总是需要多读取一个物理扇区的数据。比如要读取0号簇共4个逻辑扇区的数据,磁盘实际执行时,必须要读取0号和1号两个物理扇区共8个逻辑扇区的数据。同理,对“簇”的写入操作也是这样。显而易见,这样会造成读写性能的严重下降。 + +下面再看对齐的情况。如下图所示,分区从4号扇区开始,刚好对齐到了物理扇区1的边缘,格式化后,每个簇同样占用了4个扇区,而且这些簇都对齐到了物理扇区的边缘。 + +![为什么要分区对齐](https://raw.githubusercontent.com/supermanc88/ImageSources/master/4k-align-02.png) + +在这样对齐的情况下,当要读取某个簇,磁盘实际执行时并不需要额外读取任何扇区,可以充分发挥磁盘的读写性能。显然这正是我们需要的。 + +由此可见,对于物理扇区大小与逻辑扇区大小不一致的磁盘,分区4K对齐才能充分发挥磁盘的读写性能。而不对齐就会造成磁盘读写性能的下降。 + + + +## 如何才能对齐 + +通过前述图示的两个例子可以看到,只要将分区的起始位置对齐到物理扇区的边缘,格式化程序就会将每个簇也对齐到物理扇区的边缘,这样就实现了分区的对齐。其实对齐很简单。 + +### 如何检测物理扇区大小 + +划分分区时,要想实现4K对齐,必须首先知道磁盘物理扇区的大小。那么如何查询呢? + +打开DiskGenius软件,点击要检测的磁盘,在软件界面右侧的磁盘参数表中,可以找到“扇区大小”和“物理扇区大小”。其中“扇区大小”指的是逻辑扇区的大小。如图所示,这个磁盘的物理扇区大小为4096字节,通过计算得知,它包含了8个逻辑扇区。 + +![DiskGenius查看结果](https://raw.githubusercontent.com/supermanc88/ImageSources/master/4k-align-03.png) + +### 对齐到多少个扇区才正确 + +知道了“扇区大小”和“物理扇区大小”,用“物理扇区大小”除以“扇区大小”就能得到每个物理扇区所包含的逻辑扇区个数。这个数值就是我们要对齐的扇区个数的最小值。只要将分区起始位置对齐到这个数值的整数倍就可以了。举个例子,比如物理扇区大小是4096字节,逻辑扇区大小是512字节,那么4096除以512,等于8。我们只要将分区起始位置对齐到8的整数倍扇区就能满足分区对齐的要求。比如对齐到8、16、24、32、... 1024、2048等等。只要这个起始扇区号能够被8整除就都可以。并不是这个除数数值越大越好。Windows系统默认对齐的扇区数是2048。这个数值基本上能满足几乎所有磁盘的4K对齐要求了。 + + + +### 为什么大家都说4K对齐 + +习惯而已。因为开始出现物理扇区的概念时,多数磁盘的物理扇区大小都是4096即4K字节,习惯了就俗称4K对齐了。实际划分分区时还是要检测一下物理扇区大小,因为有些磁盘的物理扇区可能包含4个、8个、16个或者更多个逻辑扇区(总是2的n次方)。知道物理扇区大小后,再按照刚才说的计算方法,以物理扇区包含的逻辑扇区个数为基准,对齐到实际的物理扇区大小才是正确的。如果物理扇区大小是8192字节,那就要按照8192字节来对齐,严格来讲,这就不能叫4K对齐了。 + + + +### 划分分区时如何具体操作分区对齐 + +以DiskGenius软件为例,建立新分区时,在“建立新分区”对话框中勾选“对齐到下列扇区数的整数倍”,然后选择需要对齐的扇区数目,点“确定”后建立的分区就是对齐的了。如下图所示: + +![DiskGenius复制文件](https://raw.githubusercontent.com/supermanc88/ImageSources/master/4k-align-04.png) + +软件在“扇区数目”下拉框中列出了很多的选项,从中选择任意一个大于物理扇区大小的扇区数都是可以的,都能满足对齐要求。软件列出那么多的扇区数选项只是增加了选择的自由度,并不是数值越大越好。使用过大的数值可能会造成磁盘空间的浪费。软件默认的设置已经能够满足几乎所有磁盘的 4K对齐要求。 + +除了“建立新分区”对话框,DiskGenius软件还有一个“[快速分区](https://www.diskgenius.cn/help/fastpart.php)”功能,其中也有相同的对齐设置。如下图所示: + +![注册DiskGenius](https://raw.githubusercontent.com/supermanc88/ImageSources/master/4k-align-05.png) + +## 如何检测是否对齐 + +作为一款强大的分区管理软件,DiskGenius同样提供了[分区4K对齐检测](https://www.diskgenius.cn/help/4k-alignment.php)的功能。你可以用它检测一下自己硬盘的分区是否对齐了。使用方法很简单,打开软件后,首先在软件左侧选中要检测的磁盘,然后选择“工具”菜单中的“分区4KB扇区对齐检测”,软件立即显示检测结果,如下图所示: + +![注册DiskGenius](https://raw.githubusercontent.com/supermanc88/ImageSources/master/4k-align-06.png) + +最右侧“对齐”一栏是“Y”的分区就是对齐的分区,否则就是没有对齐。没有对齐的分区会用红色字体显示。 + +## 如何纠正未对齐的分区 + +对于分区已有数据但是又没有对齐的情况,DiskGenius软件目前还没有提供直接的解决方案(相信以后的版本会提供)。大家可以通过DiskGenius软件,采用分步的方法实现4K对齐。具体步骤如下: + +1、用“[备份分区](https://www.diskgenius.cn/help/part2file.php)”的功能将未对齐的分区备份到镜像文件中。 + +2、删除未对齐的分区,然后重新建立,建立时选择4K对齐。 + +3、用“[从镜像文件还原分区](https://www.diskgenius.cn/help/restorepart.php)”的功能通过第一步备份的镜像文件还原分区数据。 + + + +对于没有对齐又没有数据的分区就很简单了,删除再重建就好。 + +怎么样?通过上面的介绍,你是否对分区4K对齐有了一个比较全面的认识呢? +``` + +`逆向/Hex-Rays 十步杀一人,两步秒OLLVM-BCF.md`: + +```md +原文链接:https://bbs.pediy.com/thread-257213-1.htm + +# 什么是 Bogus Control Flow + +`Bogus Control Flow` 即虚假控制流,顾名思义,就是假的控制流,一般(OLLVM)表现为 `一条结果恒定的条件表达式` & `一个永不到达的分支`。 + +# OLLVM 中的 BCF + +OLLVM 中的虚假控制流是利用`全局变量`来构造条件表达式以制造虚假控制流,因为这个全局变量不会有写入的地方,所以这个表达式即`一条结果恒定的条件表达式`,因此始终只会走向一个真实分支,而另外的虚假分支即`一个永不到达的分支`。 大家把这个表达式称为 `"不透明谓词"` + +# 反混淆的思路 + +上面说了, 虚假控制流就是`永不到达的分支`, 只要你了解一点点的编译原理、编译器优化,就会想到,这种代码应该都是被`死代码消除(DCE, Dead Code Elimination)`的。 +所以只要能实现运行一遍 DCE 即可消除掉 BCF 混淆。 + +# 利用 IDA 反混淆 + +HexRays Decomplier肯定是有DCE优化的,但是为什么不能自动优化掉BCF? 原因出在`不透明谓词`的识别上。 + + + +之前快出 IDA7.3 的时候,就有人说 7.3 支持自动优化不透明谓词,然而拿到手之后发现并没有什么区别, 实际上经过测试,这个优化我用 6.9 测试也是存在的。 + + + +然而,使 IDA 无法识别出不透明谓词的原因是:`恒定的全局变量`是变量而不是常量,也就是说,这个变量的地址是可写而非只读的,你想办法把它改为只读,IDA 就可以进行优化了。 + +## 实测 + +随便找了一个 bcf 样本测试。优化之前是这个样子的: + + + +![图片描述](images/Hex-Rays 十步杀一人,两步秒OLLVM-BCF/715510_ZEXU63SXCH3MQZD.png) + + + +那么如何让这堆 `dword_xxx` 变成可读呢? + + + + ![图片描述](images/Hex-Rays 十步杀一人,两步秒OLLVM-BCF/715510_CWMGJQBEAHUPF2T.png) + + + +因为我的`.data` 段里没有任何其他东西,所以: + + + + ![图片描述](images/Hex-Rays 十步杀一人,两步秒OLLVM-BCF/715510_QHFSTUY7P9K7ZZ8.png) + + + +`Edit Segment`, 把 `Write` 勾掉即可。 + + + +再按一下 F5,神奇的事情就发生了~ + + + + ![图片描述](images/Hex-Rays 十步杀一人,两步秒OLLVM-BCF/715510_WKXR5ZGN36CEGQ4.png) +``` \ No newline at end of file