在接下来的文章中会讲解如何在调试器中显示局部变量和全局变量的类型和值。实现这个功能一定要有调试符号的支持,因为调试符号记录了每个变量的名称,类型,地址,长度等信息。这不是一件轻松的事情,因为你首先要对符号模型有一定的了解。所以本文的主要目的就是介绍DbgHelp中的符号模型。

符号模型

这里所说的“符号模型”指的是各种符号之间的逻辑关系,虽然微软定义了各种不同格式的符号文件,但是它们使用的符号模型都是相同的。正因为如此,使用DbgHelp可以读取各种格式的符号文件,而且DIA文档中关于符号模型的那部分内容也适用于DbgHelp,正好弥补了DbgHelp文档中缺少的内容。遗憾的是,这部分内容非常少,点到即止,不足以让人完整地学习符号模型,而且相关文档非常缺乏,或者可以说几乎没有,给使用调试符号的人带来了极大的不便。

在开发MiniDebugger的过程中,我自己也遇到了上述的窘境,在完全不了解符号模型的情况下,靠着那一点点可怜的文档,用代码来探索这个模型的原理。过程虽然艰辛,但最终还是对该模型有了一些浅显的了解。不敢说我对该模型的了解完全正确,但我仍然希望和大家分享一下这些知识,希望会对大家有所帮助。

微软的符号模型是语言无关的,它支持使用C/C++,COM,.Net等编写的程序,所以它是一个通用的模型。对于C/C++程序来说,该符号模型有很多额外的特征是用不到的,调试C/C++程序的调试器只会使用它的一个子集。

符号文件中的大部分信息都是通过符号(Symbol)来表示的,函数,变量,类型,复合类型的字段,函数参数等等都是符号。符号有两个最基本的属性:符号类型(SymTag)和符号ID(SymIndexId)。符号ID是每个符号的唯一标识符,它由符号处理器来管理,同一个符号的ID每次运行都有可能不同,甚至在同一次运行中,同一个符号通过不同方法获取到的ID都有可能不同,所以我们编写调试器的时候不应该依赖符号ID的值,它只是符号的标识符。符号类型指明了符号属于上述的哪种类型,它的取值可以是SymTagEnum枚举中的任意一个。SymTagEnum定义在cvconst.h头文件中,这个头文件属于DIA,在Visual Studio默认的包含文件夹中不存在该文件。如果嫌麻烦,可以自己定义该枚举。它的定义如下:

enum SymTagEnum {SymTagNull,SymTagExe,SymTagCompiland,SymTagCompilandDetails,SymTagCompilandEnv,SymTagFunction,         //函数SymTagBlock,SymTagData,             //变量,函数实参,复合结构字段,枚举值SymTagAnnotation,SymTagLabel,SymTagPublicSymbol,SymTagUDT,              //用户定义类型,例如struct,class和unionSymTagEnum,             //枚举类型SymTagFunctionType,    //函数类型SymTagPointerType,      //指针类型SymTagArrayType,        //数组类型SymTagBaseType,         //基本类型SymTagTypedef,          //typedef类型SymTagBaseClass,        //基类类型SymTagFriend,           //友元类型SymTagFunctionArgType,  //函数形参类型SymTagFuncDebugStart, SymTagFuncDebugEnd,SymTagUsingNamespace, SymTagVTableShape,SymTagVTable,SymTagCustom,SymTagThunk,SymTagCustomType,SymTagManagedType,SymTagDimension
};

在C/C++中可以用代码写出来的,“看得见”的类型都作了注释,下文会提及这些类型。而那些编译器定义的,“看不见”的类型,或者与C/C++无关的类型,则几乎不会用到,因此本文不会提及这些类型。

代码和数据符号

代码和数据是程序的基本组成部分, SymTagFunction和 SymTagData分别表示这两种类型的符号。代码一定在函数中,所以 SymTagFunction表示函数符号;而数据有可能是变量,常量,实参,复合类型的字段,枚举类型的值等, SymTagData统一表示这些符号。代码和数据符号的信息都可以通过 SYMBOL_INFO结构体来表示,其定义如下:

typedef struct _SYMBOL_INFO {ULONG SizeOfStruct;ULONG TypeIndex;ULONG64 Reserved[2];ULONG Index;ULONG Size;ULONG64 ModBase;ULONG Flags;ULONG64 Value;ULONG64 Address;ULONG Register;ULONG Scope;ULONG Tag;ULONG NameLen;ULONG MaxNameLen;TCHAR Name[1];
} SYMBOL_INFO,  *PSYMBOL_INFO;

下面解释每一个字段的含义:

SizeOfStruct:SYMBOL_INFO结构体的长度,在使用该结构体调用函数之前必须将这个字段设置为sizeof(SYMBOL_INFO)。

TypeIndex:符号所属类型的ID,指明了符号的类型。

Reserved:保留字段。

Index:符号的ID。

Size:符号的大小。对于函数来说,Size字段是函数所有指令的字节大小;而对于SymTagData类型的符号来说,这个字段的值通常都是0,不能通过它获取变量的大小,而应该通过变量的类型符号来获取。下文会提及这部分内容。

ModBase:符号所在模块的基地址。

Flags:这个字段的值是几个位标志的组合,描述了符号的一些性质。最常用的一个位标志是SYMFLAG_REGREL,表示符号的地址是寄存器相关的,此时Address字段保存的不是符号的虚拟地址,而是一个偏移量,需要将这个偏移量和某个寄存器的值相加才得到虚拟地址。局部变量和实参符号的Flags字段都有这个标志,它们使用EBP寄存器的值作为基址。其它位标识的含义请参考MSDN文档。

Value:符号的值。只有当符号是常量时,这个字段才有效。C/C++程序中含有const修饰符的变量并不视为常量,所以这些符号的Value字段是无效的。枚举类型的值是常量,通过Value字段可以获取每个枚举值的整型值。

Address:符号的地址。对于函数和全局变量来说,这个字段的值就是它们的虚拟地址;对于局部变量和参数来说,这个字段的值是一个偏移量,需要将其和EBP寄存器的值相加才得到虚拟地址。

Register:如果Flags字段包含SYMFLAG_REGREL,那么Register字段保存的是寄存器的标识符。通常不需要关注这个字段,因为兼容IA32的CPU总是使用EBP寄存器来访问局部变量和实参。

Scope:这个字段由DIA使用,不必关注这个字段。

Tag:符号的类型。它的值是SymTagEnum枚举中的一个。

NameLen:符号名称的长度,即Name字段中的字符个数,不包括字符串尾部的0。

MaxNameLen:指定Name字段的长度。Name字段定义为TCHAR[1],为了获取符号的名称,需要分配足够大的缓冲区,其头部是SYMBOL_INFO结构,剩下的部分用来保存符号名称,MaxNameLen就是用来指定缓冲区剩下部分的长度的。例如:

 BYTE buffer[sizeof(SYMBOL_INFO) + sizeof(TCHAR) * 128];PSYMBOL_INFO pSymInfo = (PSYMBOL_INFO)buffer;pSymInfo->MaxNameLen = 128;

Name:符号的名称。

可以通过SymFromName或SymFromAddr函数来获取某个符号信息,它们都通过SYMBOL_INFO结构体来返回信息。这两个函数的使用方法在前面的文章中已经提到过了。

类型符号

类型符号就像是代码和数据符号的元数据,描述了它们的各种属性。在 SymTagEnum枚举中, SymTagUDT, SymTagEnum, SymTagFunctionType, SymTagPointerType, SymTagArrayType, SymTagBaseType, SymTagTypedef, SymTagBaseClass, SymTagFriend和 SymTagFunctionArgType都属于类型符号。代码和数据符号通过类型符号的 ID来引用它们所属的类型, SYMBOL_INFO中的 TypeIndex就是类型符号的 ID。各种类型符号有各自的属性,在 DbgHelp中,可以通过 SymGetTypeInfo函数来获取类型符号的各种属性。该函数的声明如下:

BOOL WINAPI SymGetTypeInfo(HANDLE hProcess,DWORD64 ModBase,ULONG TypeId,IMAGEHLP_SYMBOL_TYPE_INFO GetType,PVOID pInfo
);

第一个参数是符号处理器的标识符。第二个参数 ModBase是类型符号所在模块的基地址,只有该模块具有该类型时才可以获取到它的属性。第三个参数 TypeId是类型符号的 ID。至于参数 GetType是一个 IMAGEHLP_SYMBOL_TYPE_INFO枚举,表示要获取哪种属性,同一个枚举值对于不同的类型可能有不同的意义,在下文讲解每种类型符号时会对此进行说明。这里先给出 IMAGEHLP_SYMBOL_TYPE_INFO枚举的定义:

typedef enum _IMAGEHLP_SYMBOL_TYPE_INFO {TI_GET_SYMTAG,TI_GET_SYMNAME,TI_GET_LENGTH,TI_GET_TYPE,TI_GET_TYPEID,TI_GET_BASETYPE,TI_GET_ARRAYINDEXTYPEID,TI_FINDCHILDREN,TI_GET_DATAKIND,TI_GET_ADDRESSOFFSET,TI_GET_OFFSET,TI_GET_VALUE,TI_GET_COUNT,TI_GET_CHILDRENCOUNT,TI_GET_BITPOSITION,TI_GET_VIRTUALBASECLASS,TI_GET_VIRTUALTABLESHAPEID,TI_GET_VIRTUALBASEPOINTEROFFSET,TI_GET_CLASSPARENTID,TI_GET_NESTED,TI_GET_SYMINDEX,TI_GET_LEXICALPARENT,TI_GET_ADDRESS,TI_GET_THISADJUST,TI_GET_UDTKIND,TI_IS_EQUIV_TO,TI_GET_CALLING_CONVENTION,TI_IS_CLOSE_EQUIV_TO,TI_GTIEX_REQS_VALID,TI_GET_VIRTUALBASEOFFSET,TI_GET_VIRTUALBASEDISPINDEX,TI_GET_IS_REFERENCE,TI_GET_INDIRECTVIRTUALBASECLASS,
} IMAGEHLP_SYMBOL_TYPE_INFO;

最后一个参数pInfo是一个输出参数,函数的结果通过该参数来返回。GetType参数的值不同,pInfo的实际类型也不同,下文会对此进行说明。

虽然文档中没有说明,但是SymGetTypeInfo也可以用来获取代码和数据符号的属性,这反映了所有符号都使用相同或相似的格式来保存。

下面针对每个类型符号进行详细说明。(我不会罗列每个类型符号的所有属性,只会对最基本且重要的属性进行说明)

SymTagBaseType

表示基本类型。基本类型有两个重要的属性,分别是长度和基本类型 ID。长度即该类型在内存中占用多少个字节,而基本类型 ID则指明是哪种基本类型,例如 char, int, double等。 DbgHelp通过 BaseTypeEnum枚举来表示基本类型 ID,它的定义如下:

enum BaseTypeEnum {btNoType = 0,btVoid = 1,btChar = 2,btWChar = 3,btInt = 6,btUInt = 7,btFloat = 8,btBCD = 9,btBool = 10,btLong = 13,btULong = 14,btCurrency = 25,btDate = 26,btVariant = 27,btComplex = 28,btBit = 29,btBSTR = 30,btHresult = 31
};

上文说过,微软的符号模型是通用的,不针对某种语言,所以 BaseTypeEnum中的值与 C/C++中的基本类型有些出入,不能将它们直接等同起来。为了辨别一个 C/C++的基本类型,需要同时根据长度和基本类型 ID的值来进行判断,如下表所示:

BaseTypeEnum

Length

C/C++ base type

btBool

1

bool

btChar

1

char

btUInt

1

unsigned char

btWChar

2

wchar_t

btInt

2

short

btUInt

2

unsigned short

btInt

4

int

btUInt

4

unsigned int

btLong

4

long

btULong

4

unsigned long

btInt

8

long long

btUInt

8

unsigned long long

btFloat

4

float

btFloat

8

double

长度可以使用TI_GET_LENGTH来获取,而基本类型ID可以通过TI_GET_BASETYPE来获取,此时pInfo的类型都是DWORD*。最后要注意的一点是,基本类型没有名称,不能通过TI_GET_SYMNAME获取名称。

SymTagPointerType

表示指针类型。指针类型有两个重要属性:是否引用,以及类型ID。在C++中,引用本质上是通过指针来实现的,所以指针类型和引用类型都由SymTagPointerType来表示,并通过“是否引用”这个属性来区别。而指针指向的类型的ID,则可以通过“类型ID”这个属性来获取。是否引用可以通过TI_GET_IS_REFERENCE来获取,pInfo的类型是BOOL*;类型ID可以通过TI_GET_TYPEID来获取,pInfo的类型是DWORD*。另外,指针类型也有一个长度属性,通过TI_GET_LENGTH来获取,pInfo的类型是DWORD*。在32位和64位操作系统中,指针的长度总是4个字节和8个字节,因此这个属性不怎么重要。指针类型同样没有名称。

SymTagArrayType

表示数组类型。数组类型有以下的属性:元素类型ID,元素个数以及数组长度。元素类型ID指明了数组的元素是哪种类型;元素个数的含义不言而喻了;数组长度即整个数组所占用的字节数。以上三个属性分别通过TI_GET_TYPEID,TI_GET_COUNT和TI_GET_LENGTH来获取,前两者使用的pInfo类型都是DWORD*,最后一个使用的pInfo类型是ULONG64*。数组类型也没有名称。

SymTagEnum

表示枚举类型。枚举类型有以下的属性:名称,是否嵌套,基本类型 ID,长度,枚举值个数,以及枚举值集合。名称即定义枚举时使用的名称,如果使用的是匿名类型,例如:

enum {aeOne,aeTwo,aeThree,
} anonymousEnum;

那么使用的是由编译器产生的名称。如果枚举定义在一个 UDT里面,那么“是否嵌套”属性的值就为真,此时它的名称前面会加上 UDT的名称,例如 Class::NestedEnum。在定义枚举类型的时候可以指定枚举值使用何种基本类型,基本类型 ID属性和长度属性就是用来确定这个基本类型的,它们的获取方法以及表示的含义与 SymTagBaseType类型符号相同。枚举值个数的意义不言而喻,例如上面的匿名枚举就有三个枚举值。枚举类型里的每个枚举值同样都是符号,这些符号的类型是 SymTagData,属于数据符号,不是类型符号,为了叙述方便,下文将这些符号称为“枚举值符号”。枚举值符号都是常量,都拥有名称以及值这两个属性。由于枚举类型的变量实际上保存的是枚举值的整型值,通过枚举值符号的值属性,就可以确定变量保存的是哪个枚举值。枚举类型的名称属性可以通过 TI_GET_SYMNAME获取,此时 pInfo的类型是 WCHAR**,我们不需要分配缓冲区来接收名称,因为 SymGetTypeInfo会完成这个任务,并把缓冲区的指针赋值给 *pInfo,我们要做的就是在使用完名称之后用 LocalFree释放这个缓冲区,例如:

WCHAR* pBuffer;SymGetTypeInfo(GetDebuggeeHandle(),modBase,typeID,TI_GET_SYMNAME,&pBuffer);std::wstring typeName(pBuffer);LocalFree(pBuffer);

是否嵌套可以通过 TI_GET_NESTED获取, pInfo的类型是 DWORD*,如果为真, *pInfo的值为,否则为 0。枚举个数可以通过 TI_GET_CHILDRENCOUNT获取, pInfo的类型为 DWORD*。至于枚举值符号的获取则比较麻烦,首先需要分配一块足够大的缓冲区,然后通过 TI_FINDCHILDREN来获取, pInfo的类型是 TI_FINDCHILDREN_PARAMS*。下面是示例:

TI_FINDCHILDREN_PARAMS* pFindParams =(TI_FINDCHILDREN_PARAMS*)malloc(sizeof(TI_FINDCHILDREN_PARAMS) + childrenCount * sizeof(ULONG));pFindParams->Start = 0;
pFindParams->Count = childrenCount;SymGetTypeInfo(GetDebuggeeHandle(),modBase,typeID,TI_FINDCHILDREN,pFindParams);

childrenCount表示枚举值的个数。 TI_FINDCHILDREN_PARAMS的定义如下:

 typedef struct _TI_FINDCHILDREN_PARAMS {ULONG Count;ULONG Start;ULONG ChildId[1];
} TI_FINDCHILDREN_PARAMS;

Start和Count字段分别指示从第几个枚举值开始获取(以0为基数),和获取多少个枚举值,在调用SymGetTypeInfo之前需要初始化这两个字段。而ChildID是保存枚举值符号的ID的数组,它只定义为1个元素的长度,所以我们要自己分配缓冲区来容纳所有的ID。对于枚举值符号来说,虽然它不是类型符号,但也可以通过SymGetTypeInfo来获取属性。枚举值符号的名称同样通过TI_GET_SYMNAME来获取,方法跟上文所说的一致。枚举值的整型值通过TI_GET_VALUE来获取,pInfo的类型是VARIANT*。VARIANT可以表示多种不同类型的值,可以通过不同的字段来获取这些值。下表列举了VARIANT的一些字段与C/C++基本类型之间的关系:

Field

C/C++ Base Type

boolVal

bool

cVal

char

bVal

unsigned char

uiVal

wchar_t

iVal

short

uiVal

unsigned short

intVal

int

uintVal

unsigned int

lVal

long

ulVal

unsigned long

llVal

long long

ullVal

unsigned long long

fltVal

float

dblVal

double

SymTagUDT

表示用户定义类型,例如 struct, class和 union。 UDT具有以下的属性:名称,长度,是否嵌套,种类,成员个数,成员集合。 UDT名称的注意事项和获取方法与枚举类型名称一模一样,这里就不再赘述了。长度属性表示UDT在内存中实际占用的字节数,这是对齐后的大小,通过TI_GET_LENGTH获取。是否嵌套指明该UDT是否定义在另一个UDT里面,通过TI_GET_NESTED获取。种类属性则指明了UDT是struct,class还是union,通过TI_GET_UDTKIND获取,此时pInfo的值是DWORD*。它的值使用下面的枚举:

enum UdtKind { UdtStruct,UdtClass,UdtUnion
};

这个枚举同样定义在cvconst.h中。UDT实际上是一个新的作用域,在UDT里面可以定义变量,函数和类型等,这些称为UDT的成员,成员的获取方法与枚举值的获取方法的完全一致,TI_FINDCHILDREN_PARAMS结构的ChildId数组保存的是每个成员符号的ID。每个UDT成员都是符号,例如成员方法属于代码符号,成员变量属于数据符号,而内部类型属于类型符号,实际上UDT及其成员是一个递归的定义。对于成员变量,有一个特殊的偏移量属性,指明了成员变量的地址相对于UDT地址的偏移,可以通过TI_GET_OFFSET来获取,pInfo的类型是DWORD*。

SymTagBaseClass

如果一个类继承了另一个类,那么子类会有一个特殊的成员符号,类型为SymTagBaseClass,是一个类型符号。它的属性有:名称,类型ID以及偏移,分别通过TI_GET_SYMNAME,TI_GET_TYPEID和TI_GET_OFFSET获取。名称属性是父类的名称,类型ID属性是父类的类型ID,而偏移属性是父类部分在子类对象中的偏移。子类继承了多少个父类,就有多少个SymTagBaseClass成员。

SymTagFunctionType

表示函数类型。要注意SymTagFunction和SymTagFunctionType的区别,SymTagFunction表示函数的本体,包括声明和定义;而SymTagFunctionType仅表示函数的部分声明,包括返回值和形参列表,不包括函数的名称。在函数符号的SYMBOL_INFO结构体中,TypeIndex字段就是SymTagFunctionType类型符号的ID。函数类型有以下的属性:返回值类型ID,形参个数,以及形参集合。返回值类型ID指明了返回值的类型,可以通过TI_GET_TYPEID获取。如果返回值是void,那么类型ID是SymTagBaseType类型符号的ID,因为BaseTypeEnum有一个btVoid的枚举值。形参个数通过TI_GET_CHILDRENCOUNT获取,每个形参通过TI_FINDCHILDREN获取,具体方法请参考枚举类型。

SymTagFunctionArgType

表示函数形参。SymTagFunctionType通过TI_FINDCHILDREN获取到的都是SymTagFunctionArgType类型的符号,这种符号只有一个类型ID属性,指明了形参的类型,可以通过TI_GET_TYPEID获取。由于函数形参的名称无关紧要,所以这种符号没有名称属性。

SymTagTypedef

表示使用typedef关键字定义的类型。这种符号有一个类型ID属性,指明真正的类型,可以通过TI_GET_TYPEID获取。

SymTagFriend

表示友元。我尝试了所有可能的方法,都无法获取到SymTagFriend类型的符号,所以对这种符号没有了解。

总结

通过上文的描述,相信大家对微软的符号模型已经有一定的了解,这里对此作一个总结。

在调试符号中,大部分事物都由符号来表示,符号主要有三种类型:代码,数据和类型。代码符号表示函数;数据符号表示变量,实参,枚举值以及UDT的数据成员;类型符号表示代码符号和数据符号的类型,描述了这些符号的各种属性。类型符号包括基本类型,指针类型,数组类型,枚举类型,UDT类型,函数类型,函数形参类型,基类类型,typedef类型以及友元类型。

作者:Zplutor
出处:http://www.cnblogs.com/zplutor/
本文版权归作者和博客园共有,欢迎转载。但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

类型符号的信息可以通过SymGetTypeInfo函数来获取,对GetType参数传入不同的值,就可以获取到类型的各种信息。函数通过pInfo来返回信息,pInfo的类型根据GetType的值而变化。不同的类型符号有不同的属性,有些GetType的值只对特定的类型符号有效。代码符号和数据符号的信息可以通过SymFromName或SymFromAddr获取,这两个函数通过SYMBOL_INFO结构体来返回。也可以对代码符号和数据符号使用SymGetTypeInfo来获取符号的属性。

微软的符号模型非常适合使用面向对象的思想去理解,下图是相关的类图:(图中没有友元类型符号)

[Win32]一个调试器的实现(九)符号模型相关推荐

  1. [Win32]一个调试器的实现(四)读取寄存器和内存

    [Win32]一个调试器的实现(四)读取寄存器和内存 作者:Zplutor  出处:http://www.cnblogs.com/zplutor/  本文版权归作者和博客园共有,欢迎转载.但未经作者同 ...

  2. [Win32]一个调试器的实现(五)调试符号

    一个调试器应该可以跟踪被调试程序执行到了什么地方,显示下一条将要执行的语句,显示各个变量的值,设置断点,进行单步执行等等,这些功能都需要一个基础设施的支持,那就是调试符号. 什么是调试符号 我们知道, ...

  3. [Win32]一个调试器的实现(二)调试事件的处理

    上一篇文章说到了调试循环的写法,这回讲一下调试器应该如何处理各种调试事件. RIP_EVENT 关于这种调试事件的文档资料非常少,即使提到也只是用"系统错误"或者"内部错 ...

  4. [Win32]一个调试器的实现(六)显示源代码

    上一篇文章介绍了调试符号以及DbgHelp的加载和清理,这回我们使用它来实现一个显示源代码的功能.该功能的实际使用效果如下图所示: 该功能不仅仅是显示源代码,还要显示每一行代码对应的地址.实现该功能大 ...

  5. 【C++】在 Visual Studio 调试器中指定符号 (.pdb) 和源文件(转载自RSS翻译)

     在 Visual Studio 调试器中指定符号 (.pdb) 和源文件 查找并指定符号文件和源文件:指定符号加载行为.使用符号和源服务器:自动或按需加载符号.  内容 查找符号 (.pdb) ...

  6. eac 反调试_自己动手制作一个过保护调试器

    一.起因 本人是新手第一次接触驱动开发的小白,事情是这样的,一个星期前突发奇想想做一个调试器保护程序用于调试游戏,既然要调试驱动保护的程序,自然也要深入驱动底层.做调试器必须要hook api去隐藏调 ...

  7. VC调试器高级应用(转)

    VC调试器高级应用----高级断点篇 一.位置断点修饰符  1.跳跃计数.        功能是执行断点但不在断点处停止,直到执行完了一个特定的次数为止.   使用中首先设置一个标准的位置断点,打开B ...

  8. VC调试器高级应用----系统函数,DLL段点

    一.高级断点语法 高级断点语法由两部分组成:1.上下文部分.2.位置,表达式,变量或Windows消息条件.   用函数,源文件和二进制模块来指定上下文,上下文的表示方法:   {[函数],[源文件] ...

  9. 开源项目-基于Intel VT技术的Linux内核调试器

    本开源项目将硬件虚拟化技术应用在内核调试器上,使内核调试器成为VMM,将操作系统置于虚拟机中运行,即操作系统成为GuestOS,以这样的一种形式进行调试,最主要的好处就是调试器对操作系统完全透明.如下 ...

最新文章

  1. 相机自动对焦AF原理
  2. 被批伪开源!刚刚融资6千万美元的Redis怎么了?
  3. NAACL | 通过对抗性修改,探究链接预测的鲁棒性和可解释性
  4. linux下IO口模拟I2C的一些总结
  5. 【css】报错,错误代码77,CURLE_SSL_CACERT_BADFILE (77)解决方法
  6. linux怎么查看一个文件夹的大小
  7. LaneCat网猫软件
  8. .Net Core 之 MSBuild 介绍
  9. C++ 使用模板需要注意的事情
  10. java tcp怎么拆包_Java网络编程基础之TCP粘包拆包
  11. hdu 4333 Revolving Digits
  12. 3d打印 开源_公开地图以实现可持续性,在农场进行3D打印以及更多开源新闻
  13. TCP 慢启动 拥塞控制
  14. Matlab绘制图像后在指定点绘制坐标线以及标注变量
  15. OPERA重要密码学习一
  16. 酷狗音乐分类html,酷狗音乐手机版创建歌单教程 分类自己的音乐库
  17. 2022年王道数据结构考研复习指导习题代码(排序)
  18. CSS-设置表格样式
  19. 奇迹服务器如何修改爆率,奇迹萌新教程系列-奇迹装备是否掉落配置调整
  20. postgresql集群方案hot standby初级测试(四)——xlog详细解释header

热门文章

  1. VCS使用SDF文件进行后仿反标
  2. 献给计算机非计算机专业的同学
  3. 电源正负极接反烧板的解决方法
  4. Linux下TInbsp;omap芯片nbsp;MUX…
  5. omap 的framebuffer驱动程序
  6. w7计算机u盘在哪里打开,电脑win7如何使用U盘进行重装系统
  7. 深度学习评估方法--留出法、交叉验证法、自助法
  8. python中、函数定义可以不包括以下一对圆括号_Python函数的基础学习
  9. 流媒体之从零实现搭建简单流媒体服务器,推流,拉流播放全景视频
  10. gg 修改器游戏被保护_某游戏DLL保护分析,以及偷学一点Unity代码保护思路