这个是window的SDK(software developement kit)

安装与配置 windbg 的 symbol (符号)

第一步

从 http://www.microsoft.com/ddk/debugging 下载最新版本的 WinDBG,因为符号服务器二进制文件是由 WinDBG 小组开发的。

您将需要检查是否有 WinDBG 更新版本,因为该小组似乎具有相当紧凑的发布日程安排,并且每隔几个月就会发布更新版本。

第二步

双击下载的文件安装 windbg .安装时注意记住安装到那里了.

第三步

windbg 访问符号需要两个文件(SYMSRV.DLL 和 SYMSTORE.EXE)所以添加主 path 环境变量中它们的路径进去,即:你的 windbg 安装目录.安装windows SDK,有很多个版本的,添加你需要的那个版本的即可比如X64;

PATH:

C:\Program Files (x86)\Windows Kits\10\Debuggers\x64

操作方法:在桌面我的电脑点右键—属性—高级—环境变量,在系统变量列表框中找到 path 双击,在变量值最后面加一个分号再把你的安装目录写上.点确定.
这一步是告诉 windbg 那两个文件放在什么地方.

第四步

新建一个环境变量_NT_SYMBOL_PATH 值为:

SRV*c:\mysymbol* http://msdl.microsoft.com/download/symbols

操作方法:桌面我的电脑点右键—属性—高级—环境变量 ,点击新建,把上面的变量名和变量值填上.这一步的意思是说告诉 windbg ,我的符号文件存放在c:\mysymbol 中(当然其实里面什么也没有,甚至这个文件夹也不存在,不过没关系,系统找不到的话会给你创建一个,并在上面的网址中去帮你下载符号文件放在里面)

第五步

运行 windbg 打开一个exe文件或者附加到一个进程里去, 你会看到 Symbol search path is: SRV*c:\mysymbol* http://msdl.microsoft.com/download/symbols

打开c盘看到有一个新目录 mysymbol,里面有 windbg 新下载的文件.

恭喜说明配置成功了.

因为调试程序很多时候需要查看堆栈,查看变量,所以函数的调用规则需要简单了解一下

调用约定

函数的调用约定涉及了函数参数的

1.入栈顺序

2.清栈主体(负责清理栈的主体:函数自身还是调用函数者)

3.符号命名

名称_cdecl_stdcall
入栈顺序从右向左的次序入栈从右向左的次序入栈
清栈负责者调用者(手动清栈)

被调用者(自动清栈)

符号命名下划线字符 (_) 作为名称的前缀,导出使用 C 链接的 __cdecl 函数时除外下划线 (_) 是名称的前缀。 名称后跟后面是自变量列表中的字节数(采用十进制)的符号 (@)。 因此,声明为 int func( int a, double b ) 的函数按如下所示进行修饰:_func@12
语言默认调用方式

c语言的默认调用方式

C++语言的默认调用方式

C++语言的标准调用方式

【非默认】

应用场合

变参数数量的调用

【参数数量可变】

跨语言的调用

__cdecl是CDeclaration的缩写(declaration,声明),表示C/C++和MFC程序默认使用的调用约定:所有参数从右到左依次入栈,这些参数由调用者清除,称为手动清栈。被调用函数不会要求调用者传递多少参数,调用者传递过多或者过少的参数,甚至完全不同的参数都不会产生编译阶段的错误。因此,实现可变参数的函数只能使用该调用约定。由于每一个使用__cdecl约定的函数都要包含清理堆栈的代码,所以产生的可执行文件大小会比较大。__cdecl可以写成_cdecl

  __stdcall是StandardCall的缩写,是C++的标准调用方式(不是默认),用于调用Win32 API函数:所有参数从右到左依次入栈,如果是调用类成员的话,最后一个入栈的是this指针。这些堆栈中的参数由被调用的函数在返回后清除,使用的指令是ret X,X表示参数占用的字节数,CPU在ret之后自动弹出X个字节的堆栈空间。称为自动清栈。函数在编译的时候就必须确定参数个数,并且调用者必须严格的控制参数的生成,不能多,不能少,否则返回后会出错。__stdcall可以写成_stdcall。

__fastcall 调用约定指定尽可能在寄存器中传递函数的自变量。 此调用约定仅适用于 x86 体系结构。 以下列表显示此调用约定的实现。

元素实现
参数传递顺序在自变量列表中按从左到右的顺序找到的前两个 DWORD 或更小自变量将在 ECX 和 EDX 寄存器中传递;所有其他自变量在堆栈上从右向左传递。
堆栈维护职责被调用负责维护。
名称修饰约定

At 符号 (@) 是名称的前缀;

参数列表中的字节数(在十进制中)前面的 at 符号是名称的后缀。

大小写转换约定不执行任何大小写

 总结:
1、__cdecl和__stdcall都是参数从右到左入栈。

2、__cdecl是调用者负责清除栈中的参数,如A函数中调用B函数,参数由A函数负责清除;__stdcall是被调用者负责清除栈中的参数,如A函数中调用B函数,参数由B函数负责清除。

3、_cdecl调用方式不需要知道参数的个数,若要实现变参函数,则要使用这种调用方式。而__stdcall因为参数栈空间由被调用者清除,则必须知道参数的个数(栈空间大小)。

4、__stdcall比_cdecl调用方式   __cdecl是调用者恢复堆栈的,假设有一百个不同的函数调用函数B那么内存中就有一百端恢复堆栈的代码,__stdcall是被调用者恢复堆栈,只有在函数代码的结尾出现一次恢复堆栈的代码,所以节约空间

5、恢复堆栈的代码是编译器根据你给他的call方式自动生成的,所以无需考虑,而告诉编译器call方式的意义就在这里,如果一方用__cdecl一方用__stdcall可能出现没有人释放堆栈的情况,这明显是不允许的

PASCAL 是Pascal语言的函数调用方式,也可以在C/C  中使用,参数压栈顺序与前两者相反。返回时的清栈方式忘记了。。。

__fastcall 是编译器指定的快速调用方式。由于大多数的函数参数个数很少,使用堆栈传递比较费时。因此_fastcall通常规定将前两个(或若干个)参数由寄存器传 递,其余参数还是通过堆栈传递。不同编译器编译的程序规定的寄存器不同。返回方式和_stdcall相当。(x64程序是这种方式)。被调用的函数在返回前清理传送参数的堆栈。__fastcall可以写成_fastcall。


__thiscall 是为了解决类成员调用中this指针传递而规定的。_thiscall要求把this指针放在特定寄存器中,该寄存器由编译器决定。VC使用ecx,Borland的C  编译器使用eax。返回方式和_stdcall相当。thiscall仅仅应用于“C++”成员函数。this指针存放于CX/ECX寄存器中,参数从右到左压栈。thiscall不是关键词,因此不能被程序员指定。

naked call当采用前面的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。(这些代码称作 prolog and epilog code,一般,ebp,esp的保存是必须的).但是naked call不产生这样的代码。naked call不是类型修饰符,故必须和_declspec共同使用。

1、__fastcall 和 __thiscall涉及的寄存器由编译器决定,因此不能用作跨编译器的接口。所以Windows上的COM对象接口都定义为__stdcall调用方式。

3、关键字__cdecl、__stdcall和__fastcall可以直接加在要输出的函数前,也可以在vs编译环境的Setting...->C/C++->Code Generation项选择。

当加在输出函数前的关键字与编译环境中的选择不同时,直接加在输出函数前的关键字有效。

__cdecl(默认)/Gd(默认)
__stdcall/Gz
__fastcall(X64默认)/Gr(X64默认)

4、x64程序使用__fastcall方式

5、c默认方式是__cdecl(并且只有这种方式),C++默认方式也是__cdecl,但可以修改。

x64 调用约定

本部分介绍 x64 代码中一个函数(调用方)调用另一个函数(被调用方)的标准流程和约定。

调用约定默认值
默认情况下,x64 应用程序二进制接口 (ABI) 使用四寄存器 fast-call 调用约定。 系统在调用堆栈上分配空间作为影子存储,供被调用方保存这些寄存器。

函数调用的参数与用于这些参数的寄存器之间有着严格的一一对应关系。 任何无法放入 8 字节或者不是 1、2、4 或 8 字节的参数都必须按引用传递。 单个参数永远不会分布在多个寄存器中

未使用 x87 寄存器堆栈。 被调用方可能会使用它,但请考虑到它跨函数调用的易失性。 所有浮点数运算都使用 16 个 XMM 寄存器完成。

整数参数在寄存器 RCX、RDX、R8 和 R9 中传递。 浮点数参数在 XMM0L、XMM1L、XMM2L 和 XMM3L 中传递。 16 字节参数按引用传递。 要了解参数传递,请参阅参数传递一文。 这些寄存器和 RAX、R10、R11、XMM4 和 XMM5 被视为易失,或者可能在返回时由被调用方更改。 要详细了解寄存器的使用方法,请参阅 x64 寄存器使用和由调用方/被调用方保存的寄存器。

对于原型函数,在传递参数之前,所有参数都将转换为所需的被调用方类型。 调用方负责为被调用方的参数分配空间。 调用方必须始终分配足够的空间来存储 4 个寄存器参数,即使被调用方不使用这么多参数。 此约定简化了对非原型 C 语言函数和 vararg C/C++ 函数的支持。 对于 vararg 或非原型函数,任何浮点值都必须在相应的通用寄存器中重复。 调用之前,必须将除前 4 个参数外的其他参数存储在影子存储后面的堆栈中。 要详细了解 Vararg 函数,可参阅 Vararg。 要了解非原型函数,请参阅非原型函数一文。

对齐方式
大多数结构都按其自然对齐方式对齐。 主要的例外是堆栈指针和 malloc 或 alloca 内存;为了提高性能,它们对齐到 16 字节。 若要对齐到 16 字节以上,则必须手动完成。 由于 16 字节是 XMM 运算的常见对齐大小,因此该值应当适用于大多数代码。 有关结构布局和对齐方式的详细信息,请参阅 x64 类型和存储布局。 要了解堆栈布局,请参阅 x64 堆栈使用。

展开能力
叶函数是不更改任何非易失性寄存器的函数。 非叶函数可以通过调用函数来更改非易失性 RSP。 或者,它可以通过为局部变量分配额外的堆栈空间来更改 RSP。 若要在处理异常时恢复非易失性寄存器,非叶函数使用静态数据进行注释。 数据描述如何在任意指令下正确展开函数。 此数据存储为 pdata(过程数据),后者又引用 xdata(异常处理数据) 。 xdata 包含展开信息,并且可以指向其他 pdata 或异常处理程序函数。

Prolog 和 epilog 受到严格限制,因此可以在 xdata 中对其进行正确描述。 堆栈指针必须在任何不属于 epilog 或 prolog 的代码区域中保持 16 字节对齐,但在叶函数中除外。 只需模拟返回即可展开叶函数,因此 pdata 和 xdata 不是必需的。 要详细了解函数 prolog 和 epilog 的正确结构,请参阅 x64 prolog 和 epilog。 要详细了解异常处理以及 pdata 和 xdata 的异常处理和展开,请参阅 x64 异常处理。

参数传递
默认情况下,x64 调用约定将前 4 个参数传递给寄存器中的函数。 用于这些参数的寄存器取决于参数的位置和类型。 剩余的参数按从右到左的顺序推送到堆栈上。

最左边 4 个位置的整数值参数从左到右分别在 RCX、RDX、R8 和 R9 中传递。 如前所述,第 5 个和更高位置的参数在堆栈上传递寄存器中的所有整型参数都是向右对齐的,因此被调用方可忽略寄存器的高位,只访问所需的寄存器部分

前四个参数中的所有浮点和双精度参数都在 XMM0 - XMM3(具体视位置而定)中传递。 存在 varargs 参数时,浮点值只放在整数寄存器 RCX、RDX、R8 和 R9 中。 有关详细信息,请参阅 Vararg。 同样,当相应的参数为整数或指针类型时,将忽略 XMM0 - XMM3 寄存器。

__m128 类型、数组和字符串从不通过即时值传递。 而是将指针传递给调用方分配的内存。 大小为 8、16、32 或 64 位的结构和联合以及 __m64 类型作为相同大小的整数传递。 其他大小的结构或联合作为指针传递给调用方分配的内存。 对于这些作为指针传递的聚合类型(包括 __m128),调用方分配的临时内存必须对齐 16 字节。

不分配堆栈空间且不调用其他函数的内部函数,有时使用其他易失性寄存器来传递其他寄存器参数。 编译器与内部函数实现之间的紧密绑定使此优化成为可能。

如果需要,被调用方负责将寄存器参数转储到其影子空间中。

下表总结了如何从左侧按类型和位置传递参数:

参数类型    第 5 个和更高位置    第 4 个    第3 个    第 2 个    最左侧
浮点    堆栈    XMM3    XMM2    XMM1    XMM0
整数    堆栈    R9    R8    RDX    RCX
聚合(8、16、32 或 64 位)和 __m64    堆栈    R9    R8    RDX    RCX
其他聚合,作为指针    堆栈    R9    R8    RDX    RCX
__m128,作为指针    堆栈    R9    R8    RDX    RCX
参数传递示例 1 - 所有整数
C++

复制
func1(int a, int b, int c, int d, int e, int f);
// a in RCX, b in RDX, c in R8, d in R9, f then e pushed on stack
参数传递示例 2 - 所有浮点数
C++

复制
func2(float a, double b, float c, double d, float e, float f);
// a in XMM0, b in XMM1, c in XMM2, d in XMM3, f then e pushed on stack
参数传递示例 3 - 整数和浮点数混合
C++

复制
func3(int a, double b, int c, float d, int e, float f);
// a in RCX, b in XMM1, c in R8, d in XMM3, f then e pushed on stack
参数传递示例 4 - __m64、__m128 和聚合
C++

复制
func4(__m64 a, __m128 b, struct c, float d, __m128 e, __m128 f);
// a in RCX, ptr to b in RDX, ptr to c in R8, d in XMM3,
// ptr to f pushed on stack, then ptr to e pushed on stack
Vararg
如果通过 vararg(例如省略号参数)传递参数,则需遵守常规寄存器参数传递约定。 该约定规定了将第 5 个及后面的参数溢出到堆栈中。 被调用方负责转储带有其地址的参数。 (仅适用于浮点值)如果被调用方希望在整数寄存器中使用浮点值,则整数寄存器和浮点数寄存器都必须包含该值。

非原型函数
对于尚未完全原型化的函数,调用方将整数值作为整数传递,将浮点值作为双精度数传递。 (仅适用于浮点值)如果被调用方希望在整数寄存器中使用浮点值,则整数寄存器和浮点数寄存器都必须包含该值。

C++

复制
func1();
func2() {   // RCX = 2, RDX = XMM1 = 1.0, and R8 = 7
   func1(2, 1.0, 7);
}
返回值
可容纳 64 位(包括 __m64 类型)的标量返回值是通过 RAX 返回的。 非标量类型(包括浮点类型、双精度类型和向量类型,例如 __m128、__m128i、__m128d)以 XMM0 的形式返回。 返回到 RAX 或 XMM0 中的值的未使用位数的状态未定义。

用户定义类型可以从全局函数和静态成员函数通过值返回。 若要将用户定义类型通过值返回到 RAX 中,其长度必须为 1、2、4、8、16、32 或 64 位。 它还必须没有用户定义的构造函数、析构函数或复制赋值运算符。 它不能具有私有或受保护的非静态数据成员,也不能具有引用类型的非静态数据成员。 它不能具有基类或虚拟函数。 而且,它只能有同样满足这些要求的数据成员。 (此定义基本上与 C++03 POD 类型相同。由于 C++11 标准中的定义已更改,我们不建议使用 std::is_pod 进行此测试。)否则,调用方必须为返回值分配内存,并将指向它的指针作为第一个参数传递。 然后,其余参数将向右移动一个参数。 相同的指针必须在 RAX 中被调用方返回。

x64 寄存器使用情况

x64 体系结构提供了 16 个通用寄存器(以后称为整数寄存器),以及 16 个可供浮点使用的 XMM/YMM 寄存器。 易失寄存器是由调用方假想的临时寄存器,并要在调用过程中销毁非易失寄存器需要在整个函数调用过程中保留其值,并且一旦使用,则必须由被调用方保存

寄存器的易失性和保存方式

下表说明了每种寄存器在整个函数调用过程中的使用方法:

寄存器状态使用
RAX易失的返回值寄存器
RCX易失的第一个整型自变量
RDX易失的第二个整型自变量
R8易失的第三个整型自变量
R9易失的第四个整型自变量
R10:R11易失的必须根据需要由调用方保留;在 syscall/sysret 指令中使用
R12:R15非易失的必须由被调用方保留
RDI非易失的必须由被调用方保留
RSI非易失的必须由被调用方保留
RBX非易失的必须由被调用方保留
RBP非易失的可用作帧指针;必须由被调用方保留
RSP非易失的堆栈指针
XMM0、YMM0易失的第一个 FP 参数;使用 __vectorcall 时的第一个矢量类型参数
XMM1、YMM1易失的第二个 FP 参数;使用 __vectorcall 时的第二个矢量类型参数
XMM2、YMM2易失的第三个 FP 参数;使用 __vectorcall 时的第三个矢量类型参数
XMM3、YMM3易失的第四个 FP 自变量;使用 __vectorcall 时的第四个矢量类型参数
XMM4、YMM4易失的必须根据需要由调用方保留;使用 __vectorcall 时的第五个矢量类型参数
XMM5、YMM5易失的必须根据需要由调用方保留;使用 __vectorcall 时的第六个矢量类型参数
XMM6:XMM15、YMM6:YMM15非易失的 (XMM),易失的(YMM 的上半部分)必须由被调用方保留。 YMM 寄存器必须根据需要由调用方保留。

当函数进入和退出 C 运行时库调用和 Windows 系统调用时,CPU 标志寄存器的方向位标志将被清除。

x64 堆栈使用

所有超出 RSP 当前地址的内存都被视为易失性内存:操作系统或调试器可能会在用户调试会话或中断处理程序期间覆盖此内存。 因此,在尝试对堆栈帧读取或写入值之前,必须始终设置 RSP。

本节讨论局部变量的堆栈空间分配和 alloca 内部函数。

堆栈分配

函数的 prolog 负责为局部变量、保存的寄存器、堆栈参数和寄存器参数分配堆栈空间。

参数区域始终位于堆栈底部(即使使用 alloca),以便它在任何函数调用期间都始终与返回地址相邻。 它至少包含四个条目,但始终包含足够的空间来保存任何可能被调用的函数所需的所有参数。 请注意,系统始终为寄存器参数分配空间,即使这些参数本身从不驻留在堆栈中;会向被调用方保证为其所有参数分配了空间。 寄存器参数需要主地址,因此,如果所调用的函数需要获取参数列表 (va_list) 或单独参数的地址,则可使用连续区域。 此区域还提供了一个方便位置,用于在 thunk 执行期间保存寄存器参数,并作为调试选项(例如,如果参数存储在 prolog 代码中的主地址处,则可以在调试期间轻松查找参数)。 即使所调用的函数的参数少于 4 个,这 4 个堆栈位置实际上由所调用的函数所拥有,并且可能会由所调用的函数用于除保存参数寄存器值之外的其他用途。 因此,调用方可能不会在函数调用中将信息保存在此堆栈区域中。

如果在函数中动态分配空间 (alloca),则必须使用非易失性寄存器作为帧指针来标记堆栈固定部分的基址,并且必须在 prolog 中保存和初始化该寄存器。 请注意,使用 alloca 时,从同一个调用方对同一个被调用方进行的调用对于其寄存器参数可能具有不同的主地址。

堆栈将始终保持为 16 字节对齐,除非是在 prolog 中(例如,压入返回地址之后),以及除非在某类帧函数的函数类型中指明。

下面是堆栈布局的一个示例,其中函数 A 调用非叶函数 B。函数 A 的 prolog 已在堆栈底部为 B 所需的所有寄存器和堆栈参数分配了空间。 此调用会压入返回地址,而 B 的 prolog 会为其局部变量、非易失性寄存器以及它调用函数所需的空间分配空间。 如果 B 使用 alloca,则在局部变量/非易失性寄存器保存区域与参数堆栈区域之间分配空间。

当函数 B 调用另一个函数时,返回地址会压入到 RCX 的主地址正下方。

动态参数堆栈区域构造

如果使用帧指针,则可选择动态创建参数堆栈区域。 当前在 x64 编译器中未实现此操作。

函数类型

主要有两种类型的函数。 需要堆栈帧的函数称为帧函数 。 不需要堆栈帧的函数称为叶函数 。

帧函数是分配堆栈空间、调用其他函数、保存非易失性寄存器或使用异常处理的函数。 它还需要函数表条目。 帧函数需要 prolog和 epilog。 帧函数可以动态分配堆栈空间,并可以使用帧指针。 帧函数可自行使用此调用标准的所有功能。

如果帧函数不调用另一个函数,则不需要使堆栈对齐(在堆栈分配一节中涉及)。

叶函数是不需要函数表条目的函数。 它无法更改任何非易失性寄存器(包括 RSP),这意味着它无法调用任何函数或分配堆栈空间。 它在执行时可以使堆栈保持未对齐状态。

malloc 对齐

malloc 保证返回适当对齐的内存,用于存储任何具有基本对齐以及可以适合所分配的内存量的对象。 基本对齐 是在没有对齐规范的情况下,小于或等于实现所支持的最大对齐的对齐。 (在 Visual C++ 中,这是 double 或 8 字节所需的对齐方式。在面向 64 位平台的代码中,则是 16 字节。)例如,4 字节分配将在支持任何 4 字节或更小对象的边界上对齐。

Visual C++ 允许使用具有扩展对齐 的类型,这些类型也称为过度对齐 类型。 例如,SSE 类型 __m128 和 __m256 以及使用 __declspec(align( n ))(其中 n 大于 8)声明的类型具有扩展对齐。 malloc 不保证内存在适合于需要扩展对齐的对象的边界上对齐。 若要为过度对齐类型分配内存,请使用 _aligned_malloc 和相关函数。

alloca

_alloca 需要是 16 字节对齐,此外需要使用帧指针。

分配的堆栈需要在它后面包含用于后续调用函数的参数的空间,如堆栈分配中所述。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值