如果函数中修改了它, 会有影响吗?, 它的性质是否和EAX, ECX,EDX 一样是通用寄存器, 可以直接在函数中修改它, 而不需要保存与恢复.
写一段完美无损(只多两条指令)调用memcpy, 将指令地址保存在了ebx
这样通过VBA申明CopyMem方便调用cdel 的 memcpy
#pragma function(memcpy)
__declspec(naked) void __stdcall CopyMem(void * Dst, const void * Src, size_t Size)
{
__asm
{
pop ebx //保存调用 CopyMem 的 eip (下一条指令) 至 ebx
call dword ptr [memcpy] // call 会保存 eip 以及 esp 减4
mov dword ptr [esp], ebx //恢复栈区的 eip 数据
ret 08h
}
}
#pragma intrinsic(memcpy)
14 个解决方案
#1
单纯的汇编,用 ebx 的不会少吧;高级语言里,通常被作为寄存器变量或优化时用到的中间变量。
按照寄存器上的使用约定,这里用 ebx 是不大好理解,在函数里对其进行修改又没保护性动作,后果是很难预料的。
按照寄存器上的使用约定,这里用 ebx 是不大好理解,在函数里对其进行修改又没保护性动作,后果是很难预料的。
#2
你理解错了吧
eax, ecx 和 edx 这三个通用寄存器肯定不是需要保存的. 你随便用C建一个无返回的void函数, 再看汇编, 编译器是根本不会保存.
当然 eax默认用于有返回值的函数.
我这里用ebx, 是因为memcpy 没有用到ebx
我发这个贴只是深入了解一下ebx, 虽然字面是 基址寄存器 意思
当然你不能说修改EBX后有难预料的后果, 必须给个例证.
#3
例证,很简单了,写个函数,里面定义上 8 个 int 类型变量,赋值了,输出它们,调用你那个 CopyMem(),然后再输出那些 int 的值。将这个代码,分别以速度优化和不优化生成可执行文件,分别运行这两个程序,看看输出结果。
还是不明白, cl /c /Fas /Od xx.cpp 及 cl /c /Fas /Ox xx.cpp 生成相应的汇编,看看汇编里对局部变量的处理上的不同,以及对 ebx 的涉及。
还是不明白, cl /c /Fas /Od xx.cpp 及 cl /c /Fas /Ox xx.cpp 生成相应的汇编,看看汇编里对局部变量的处理上的不同,以及对 ebx 的涉及。
#4
你错了, 不过你这段也对我有所提示
这两天在研究google-code-prettify时, 想修改关键字, 顺便看看 volatile.! 对我来说惊天发现
不管你DEBUG/RELEASE, 还是X优化. 函数的调用会告诉编译器, 4个通用寄存器会重写, 即不要将变量缓存到寄存器,因为函数(这段代码)可能会用到通用寄存器.
测试代码还没写, 大致如下
声明4个变量
4个变量入4个寄存器
printf 4个变量(printf应该也一样)
或 你的函数调用(注意:即使release也能调用用)
再次 printf 4个变量, (查看是不是从寄存器, 还是栈区)
#5
但是调用CopyMem的上下文可能用到,你中间把ebx改了,后果不好说,可能没问题也可能很严重。另外这也与用的编译器有关。
#6
是的, 修改ebx 隐藏的风险很严重.
那我的 CopyMem 在 __declspec(naked) 下怎么高效的写呢? 我可不想重新再将参数压栈出栈.
to: zara
刚才跟踪汇编, 是我错了, 但你没讲得很明白, 可能还是我太菜了
#7
搞几个小时,学到不少相关知识:
内存屏障--- asm volatile("" ::: "memory")
http://blog.csdn.net/kissmonx/article/details/9105823
C语言的那些小秘密之volatile
http://blog.csdn.net/bigloomy/article/details/6645810
内存屏障--- asm volatile("" ::: "memory")
http://blog.csdn.net/kissmonx/article/details/9105823
C语言的那些小秘密之volatile
http://blog.csdn.net/bigloomy/article/details/6645810
#8
测试代码. 测试于VS2010.
总结:
关于EAX, ECX, EDX.
在call 被调用函数前, 编译器(100%可能)作废寄存器EAX, ECX, EDX.内的数据(变量).
在call 被调用函数后, 编译器(100%可能)覆写寄存器EAX, ECX, EDX内的数据(变量).
关于EBX, EDI, ESI.
调用函数在call 被调用函数后, 编译器(100%可能)直接读取EBX, EDI, ESI.内的数据(变量), 而不会从栈区重取变量.
那么, 被调用函数如果有对EBX, ESI, EDI中的数据进行修改, 那它必须负责保存与恢复.
因此, 我们常常看到
函数汇编开始
push ebx
push esi
push edi
函数汇编结束
pop ebx
pop esi
pop edi
void general_register_test(int ax, int bx, int cx, int dx)
{
__asm
{
mov eax, 1
mov ebx, 2
mov ecx, 3
mov edx, 4
mov esi, 5
mov edi, 6
}
}
__declspec(naked) void general_register_naked_test(int ax, int bx, int cx, int dx)
{
__asm
{
mov eax, 1
mov ebx, 2
mov ecx, 3
mov edx, 4
mov esi, 5
mov edi, 6
ret
}
}
int main()
{
int ax=getchar();
int bx=getchar(), cx=getchar(), dx=getchar();
int di=getchar(), si=getchar();
ax++; bx++; cx++; dx++; di++; si++;
printf("初始值: %x,%x,%x,%x,%x,%x\n", ax, bx, cx, dx, di, si);
ax++; bx++; cx++; dx++; di++; si++;
general_register_test(ax, bx, cx, dx);
//general_register_naked_test(ax, bx, cx, dx);
printf("调用后: %x,%x,%x,%x,%x,%x\n", ax, bx, cx, dx, di, si);
return 0;
}
总结:
关于EAX, ECX, EDX.
在call 被调用函数前, 编译器(100%可能)作废寄存器EAX, ECX, EDX.内的数据(变量).
在call 被调用函数后, 编译器(100%可能)覆写寄存器EAX, ECX, EDX内的数据(变量).
关于EBX, EDI, ESI.
调用函数在call 被调用函数后, 编译器(100%可能)直接读取EBX, EDI, ESI.内的数据(变量), 而不会从栈区重取变量.
那么, 被调用函数如果有对EBX, ESI, EDI中的数据进行修改, 那它必须负责保存与恢复.
因此, 我们常常看到
函数汇编开始
push ebx
push esi
push edi
函数汇编结束
pop ebx
pop esi
pop edi
#9
我记得在编译学里面这种上下文相关的寄存器叫非杂合寄存器
放断某语言的参考文档吧,vc下也是这样我试过
13.1.10.1 寄存器协定
• EAX、ECX、EDX 都是杂合(scratch)寄存器,并且会被函数破坏(destroy) 。
• EBX、ESI、EDI、EBP 在跨函数调用时都需要被保护。
• EFLAGS 在跨函数调用时被假定破坏了,除了值必须为向前的方向标志以外。
• FPU 栈在调用一个函数时必须为空。
• FPU 控制字在跨函数调用时必须被保护。
• 浮点返回值被返回在 FPU 栈里。即使不使用它们,也需要调用者将它们清除干净。
放断某语言的参考文档吧,vc下也是这样我试过
13.1.10.1 寄存器协定
• EAX、ECX、EDX 都是杂合(scratch)寄存器,并且会被函数破坏(destroy) 。
• EBX、ESI、EDI、EBP 在跨函数调用时都需要被保护。
• EFLAGS 在跨函数调用时被假定破坏了,除了值必须为向前的方向标志以外。
• FPU 栈在调用一个函数时必须为空。
• FPU 控制字在跨函数调用时必须被保护。
• 浮点返回值被返回在 FPU 栈里。即使不使用它们,也需要调用者将它们清除干净。
#10
我这里用ebx, 是因为memcpy 没有用到ebx
但是调用CopyMem的上下文可能用到,你中间把ebx改了,后果不好说,可能没问题也可能很严重。另外这也与用的编译器有关。
是的, 修改ebx 隐藏的风险很严重.
那我的 CopyMem 在 __declspec(naked) 下怎么高效的写呢? 我可不想重新再将参数压栈出栈.
to: zara
刚才跟踪汇编, 是我错了, 但你没讲得很明白, 可能还是我太菜了
调痈约定相同可以直接jmp直接这样写就可以了
__declspec(naked) void __cdecl CopyMem(void * Dst, const void * Src, size_t Size)
{
__asm jmp dword ptr [memcpy]
}
或者
__declspec(naked) void __stdcall CopyMem(void * Dst, const void * Src, size_t Size)
{
__asm
{
call dword ptr [memcpy]
ret 12
}
}
#11
__declspec(naked) void __stdcall CopyMem(void * Dst, const void * Src, size_t Size)
{
__asm
{
call dword ptr [memcpy]
ret 12
}
}
============================================================================
第二个写错了,貌似没别的好办法。
{
__asm
{
call dword ptr [memcpy]
ret 12
}
}
============================================================================
第二个写错了,貌似没别的好办法。
#12
我记得在编译学里面这种上下文相关的寄存器叫非杂合寄存器
放断某语言的参考文档吧,vc下也是这样我试过
13.1.10.1 寄存器协定
• EAX、ECX、EDX 都是杂合(scratch)寄存器,并且会被函数破坏(destroy) 。
• EBX、ESI、EDI、EBP 在跨函数调用时都需要被保护。
• EFLAGS 在跨函数调用时被假定破坏了,除了值必须为向前的方向标志以外。
• FPU 栈在调用一个函数时必须为空。
• FPU 控制字在跨函数调用时必须被保护。
• 浮点返回值被返回在 FPU 栈里。即使不使用它们,也需要调用者将它们清除干净。
非常感谢! 这个跟我的测试不谋而合.
能说一下这是什么手册? 这样我少走一些弯路.
关于dll中调用cdecl函数然后公开(即达到转发器的效果). 最佳代码, 其实很简单.创建一个(静态)模块级变量, 用于保存EIP的执行指令, 然后4句代码.
1, pop 静态变量
2, call cdecl函数
4, push 静态变量
5, ret x
如果cdecl函数的参数就一两个直接push 参数好了, 如果参数在两个以上, 当然用这个, 即方便又快.
#13
我记得在编译学里面这种上下文相关的寄存器叫非杂合寄存器
放断某语言的参考文档吧,vc下也是这样我试过
13.1.10.1 寄存器协定
• EAX、ECX、EDX 都是杂合(scratch)寄存器,并且会被函数破坏(destroy) 。
• EBX、ESI、EDI、EBP 在跨函数调用时都需要被保护。
• EFLAGS 在跨函数调用时被假定破坏了,除了值必须为向前的方向标志以外。
• FPU 栈在调用一个函数时必须为空。
• FPU 控制字在跨函数调用时必须被保护。
• 浮点返回值被返回在 FPU 栈里。即使不使用它们,也需要调用者将它们清除干净。
非常感谢! 这个跟我的测试不谋而合.
能说一下这是什么手册? 这样我少走一些弯路.
关于dll中调用cdecl函数然后公开(即达到转发器的效果). 最佳代码, 其实很简单.创建一个(静态)模块级变量, 用于保存EIP的执行指令, 然后4句代码.
1, pop 静态变量
2, call cdecl函数
4, push 静态变量
5, ret x
如果cdecl函数的参数就一两个直接push 参数好了, 如果参数在两个以上, 当然用这个, 即方便又快.
附图
#14
创建时间: 2016-01-13, 最后更新: 2016-01-13, 来源: 原创/翻译 版本: V1.0
说明备注: 全新翻译;
参考链接: https://msdn.microsoft.com/zh-cn/library/k1a8ss06(v=vs.100).aspx
一般情况下,__asm区块开始时,您不应该假设寄存器将会有指定的值。寄存器值不一定会保留至不同的__asm区块。如果您结束一个在线码区块,并开始另一个,第二个区块寄存器不能依赖第一个区块保留的值。 __asm区块会继承正常控制流的寄存器值结果。
如果您使用__fastcall调用约定时,编译器在寄存器中传递函数参数,而不是在堆栈上。这可能会导致函数中创建 __asm 块出现问题,因为函数没有办法告诉哪个参数是哪个寄存器中。如果函数已经有在 EAX 接收一个参数,但又立即将其他部分存储在 EAX 中的,那么原始参数将会丢失。此外,任何 __fastcall 声明的函数,必须保留 ECX 寄存器.
若要避免此类寄存器的冲突,请不要使用 __fastcall 调用约定的函数去包含一个 __asm 块。如果使用 /Gr 编译器选项来指定默认的调用约定为 __fastcall ,那么函数声明必须使用 __cdecl 或 __stdcall 才能包含 __asm块(__cdecl 特性通知编译器为该函数使用 C 调用约定)。 如果您不使用 /Gr 编译,避免声明函数时使用 __fastcall 特性。
当使用 __asm 在 C/C++ 函数中编写汇编语言,您不需要保留 EAX、 EBX、 ECX、 EDX,ESI 或 EDI 寄存器。例如,示例 POWER2 (在“使用在线汇编编写函数”中)。power2 函数并不会保留在 EAX 寄存器中的值。但是,使用这些寄存器会影响代码质量,因为寄存器分配器就不能使用它们跨 __asm 块来存储值。此外,在在线汇编代码中使用 EBX、 ESI 或 EDI,你必须在函数 prologue(入口) 与 epilogue(出口),强制编译器来保存和还原这些寄存器。
__asm块中,应该保留对其他寄存器的使用 (例如 DS、 SS、 SP、 BP、 和标志寄存器) 。除非您有一些理由更改它们 (如切换堆栈),您应该保留 ESP 和 EBP 寄存器。请参阅“优化汇编代码”。
某些 SSE 类型需要堆栈 8 字节对齐方式,强制编译器将发出动态堆栈对齐方式的代码。只有在对齐方式之后,才能存取局部变量和函数参数,编译器会维护两个框架指针(ESP,EBP)。如果编译器执行框架指针省略 (FPO:frame pointer omission),它将会使用 EBP 和 ESP。如果编译器没有执行 FPO,它将会使用 EBX 和 EBP。如果函数需要动态堆栈对齐,为确保代码运行正常,在asm 代码中不要修改 EBX,因为它可能用来修改框架指针。要么函数中没有移动 8 个字节对齐的类型,要么避免使用 EBX。
注意
如果在线汇编代码使用指令 STD 或 CLD 修改了方向标志,则必须还原标志的原始值。
特意结个贴!
说明备注: 全新翻译;
参考链接: https://msdn.microsoft.com/zh-cn/library/k1a8ss06(v=vs.100).aspx
一般情况下,__asm区块开始时,您不应该假设寄存器将会有指定的值。寄存器值不一定会保留至不同的__asm区块。如果您结束一个在线码区块,并开始另一个,第二个区块寄存器不能依赖第一个区块保留的值。 __asm区块会继承正常控制流的寄存器值结果。
如果您使用__fastcall调用约定时,编译器在寄存器中传递函数参数,而不是在堆栈上。这可能会导致函数中创建 __asm 块出现问题,因为函数没有办法告诉哪个参数是哪个寄存器中。如果函数已经有在 EAX 接收一个参数,但又立即将其他部分存储在 EAX 中的,那么原始参数将会丢失。此外,任何 __fastcall 声明的函数,必须保留 ECX 寄存器.
若要避免此类寄存器的冲突,请不要使用 __fastcall 调用约定的函数去包含一个 __asm 块。如果使用 /Gr 编译器选项来指定默认的调用约定为 __fastcall ,那么函数声明必须使用 __cdecl 或 __stdcall 才能包含 __asm块(__cdecl 特性通知编译器为该函数使用 C 调用约定)。 如果您不使用 /Gr 编译,避免声明函数时使用 __fastcall 特性。
当使用 __asm 在 C/C++ 函数中编写汇编语言,您不需要保留 EAX、 EBX、 ECX、 EDX,ESI 或 EDI 寄存器。例如,示例 POWER2 (在“使用在线汇编编写函数”中)。power2 函数并不会保留在 EAX 寄存器中的值。但是,使用这些寄存器会影响代码质量,因为寄存器分配器就不能使用它们跨 __asm 块来存储值。此外,在在线汇编代码中使用 EBX、 ESI 或 EDI,你必须在函数 prologue(入口) 与 epilogue(出口),强制编译器来保存和还原这些寄存器。
__asm块中,应该保留对其他寄存器的使用 (例如 DS、 SS、 SP、 BP、 和标志寄存器) 。除非您有一些理由更改它们 (如切换堆栈),您应该保留 ESP 和 EBP 寄存器。请参阅“优化汇编代码”。
某些 SSE 类型需要堆栈 8 字节对齐方式,强制编译器将发出动态堆栈对齐方式的代码。只有在对齐方式之后,才能存取局部变量和函数参数,编译器会维护两个框架指针(ESP,EBP)。如果编译器执行框架指针省略 (FPO:frame pointer omission),它将会使用 EBP 和 ESP。如果编译器没有执行 FPO,它将会使用 EBX 和 EBP。如果函数需要动态堆栈对齐,为确保代码运行正常,在asm 代码中不要修改 EBX,因为它可能用来修改框架指针。要么函数中没有移动 8 个字节对齐的类型,要么避免使用 EBX。
注意
如果在线汇编代码使用指令 STD 或 CLD 修改了方向标志,则必须还原标志的原始值。
特意结个贴!
#1
单纯的汇编,用 ebx 的不会少吧;高级语言里,通常被作为寄存器变量或优化时用到的中间变量。
按照寄存器上的使用约定,这里用 ebx 是不大好理解,在函数里对其进行修改又没保护性动作,后果是很难预料的。
按照寄存器上的使用约定,这里用 ebx 是不大好理解,在函数里对其进行修改又没保护性动作,后果是很难预料的。
#2
单纯的汇编,用 ebx 的不会少吧;高级语言里,通常被作为寄存器变量或优化时用到的中间变量。
按照寄存器上的使用约定,这里用 ebx 是不大好理解,在函数里对其进行修改又没保护性动作,后果是很难预料的。
你理解错了吧
eax, ecx 和 edx 这三个通用寄存器肯定不是需要保存的. 你随便用C建一个无返回的void函数, 再看汇编, 编译器是根本不会保存.
当然 eax默认用于有返回值的函数.
我这里用ebx, 是因为memcpy 没有用到ebx
我发这个贴只是深入了解一下ebx, 虽然字面是 基址寄存器 意思
当然你不能说修改EBX后有难预料的后果, 必须给个例证.
#3
例证,很简单了,写个函数,里面定义上 8 个 int 类型变量,赋值了,输出它们,调用你那个 CopyMem(),然后再输出那些 int 的值。将这个代码,分别以速度优化和不优化生成可执行文件,分别运行这两个程序,看看输出结果。
还是不明白, cl /c /Fas /Od xx.cpp 及 cl /c /Fas /Ox xx.cpp 生成相应的汇编,看看汇编里对局部变量的处理上的不同,以及对 ebx 的涉及。
还是不明白, cl /c /Fas /Od xx.cpp 及 cl /c /Fas /Ox xx.cpp 生成相应的汇编,看看汇编里对局部变量的处理上的不同,以及对 ebx 的涉及。
#4
例证,很简单了,写个函数,里面定义上 8 个 int 类型变量,赋值了,输出它们,调用你那个 CopyMem(),然后再输出那些 int 的值。将这个代码,分别以速度优化和不优化生成可执行文件,分别运行这两个程序,看看输出结果。
还是不明白, cl /c /Fas /Od xx.cpp 及 cl /c /Fas /Ox xx.cpp 生成相应的汇编,看看汇编里对局部变量的处理上的不同,以及对 ebx 的涉及。
你错了, 不过你这段也对我有所提示
这两天在研究google-code-prettify时, 想修改关键字, 顺便看看 volatile.! 对我来说惊天发现
不管你DEBUG/RELEASE, 还是X优化. 函数的调用会告诉编译器, 4个通用寄存器会重写, 即不要将变量缓存到寄存器,因为函数(这段代码)可能会用到通用寄存器.
测试代码还没写, 大致如下
声明4个变量
4个变量入4个寄存器
printf 4个变量(printf应该也一样)
或 你的函数调用(注意:即使release也能调用用)
再次 printf 4个变量, (查看是不是从寄存器, 还是栈区)
#5
我这里用ebx, 是因为memcpy 没有用到ebx
但是调用CopyMem的上下文可能用到,你中间把ebx改了,后果不好说,可能没问题也可能很严重。另外这也与用的编译器有关。
#6
我这里用ebx, 是因为memcpy 没有用到ebx
但是调用CopyMem的上下文可能用到,你中间把ebx改了,后果不好说,可能没问题也可能很严重。另外这也与用的编译器有关。
是的, 修改ebx 隐藏的风险很严重.
那我的 CopyMem 在 __declspec(naked) 下怎么高效的写呢? 我可不想重新再将参数压栈出栈.
to: zara
刚才跟踪汇编, 是我错了, 但你没讲得很明白, 可能还是我太菜了
#7
搞几个小时,学到不少相关知识:
内存屏障--- asm volatile("" ::: "memory")
http://blog.csdn.net/kissmonx/article/details/9105823
C语言的那些小秘密之volatile
http://blog.csdn.net/bigloomy/article/details/6645810
内存屏障--- asm volatile("" ::: "memory")
http://blog.csdn.net/kissmonx/article/details/9105823
C语言的那些小秘密之volatile
http://blog.csdn.net/bigloomy/article/details/6645810
#8
测试代码. 测试于VS2010.
总结:
关于EAX, ECX, EDX.
在call 被调用函数前, 编译器(100%可能)作废寄存器EAX, ECX, EDX.内的数据(变量).
在call 被调用函数后, 编译器(100%可能)覆写寄存器EAX, ECX, EDX内的数据(变量).
关于EBX, EDI, ESI.
调用函数在call 被调用函数后, 编译器(100%可能)直接读取EBX, EDI, ESI.内的数据(变量), 而不会从栈区重取变量.
那么, 被调用函数如果有对EBX, ESI, EDI中的数据进行修改, 那它必须负责保存与恢复.
因此, 我们常常看到
函数汇编开始
push ebx
push esi
push edi
函数汇编结束
pop ebx
pop esi
pop edi
void general_register_test(int ax, int bx, int cx, int dx)
{
__asm
{
mov eax, 1
mov ebx, 2
mov ecx, 3
mov edx, 4
mov esi, 5
mov edi, 6
}
}
__declspec(naked) void general_register_naked_test(int ax, int bx, int cx, int dx)
{
__asm
{
mov eax, 1
mov ebx, 2
mov ecx, 3
mov edx, 4
mov esi, 5
mov edi, 6
ret
}
}
int main()
{
int ax=getchar();
int bx=getchar(), cx=getchar(), dx=getchar();
int di=getchar(), si=getchar();
ax++; bx++; cx++; dx++; di++; si++;
printf("初始值: %x,%x,%x,%x,%x,%x\n", ax, bx, cx, dx, di, si);
ax++; bx++; cx++; dx++; di++; si++;
general_register_test(ax, bx, cx, dx);
//general_register_naked_test(ax, bx, cx, dx);
printf("调用后: %x,%x,%x,%x,%x,%x\n", ax, bx, cx, dx, di, si);
return 0;
}
总结:
关于EAX, ECX, EDX.
在call 被调用函数前, 编译器(100%可能)作废寄存器EAX, ECX, EDX.内的数据(变量).
在call 被调用函数后, 编译器(100%可能)覆写寄存器EAX, ECX, EDX内的数据(变量).
关于EBX, EDI, ESI.
调用函数在call 被调用函数后, 编译器(100%可能)直接读取EBX, EDI, ESI.内的数据(变量), 而不会从栈区重取变量.
那么, 被调用函数如果有对EBX, ESI, EDI中的数据进行修改, 那它必须负责保存与恢复.
因此, 我们常常看到
函数汇编开始
push ebx
push esi
push edi
函数汇编结束
pop ebx
pop esi
pop edi
#9
我记得在编译学里面这种上下文相关的寄存器叫非杂合寄存器
放断某语言的参考文档吧,vc下也是这样我试过
13.1.10.1 寄存器协定
• EAX、ECX、EDX 都是杂合(scratch)寄存器,并且会被函数破坏(destroy) 。
• EBX、ESI、EDI、EBP 在跨函数调用时都需要被保护。
• EFLAGS 在跨函数调用时被假定破坏了,除了值必须为向前的方向标志以外。
• FPU 栈在调用一个函数时必须为空。
• FPU 控制字在跨函数调用时必须被保护。
• 浮点返回值被返回在 FPU 栈里。即使不使用它们,也需要调用者将它们清除干净。
放断某语言的参考文档吧,vc下也是这样我试过
13.1.10.1 寄存器协定
• EAX、ECX、EDX 都是杂合(scratch)寄存器,并且会被函数破坏(destroy) 。
• EBX、ESI、EDI、EBP 在跨函数调用时都需要被保护。
• EFLAGS 在跨函数调用时被假定破坏了,除了值必须为向前的方向标志以外。
• FPU 栈在调用一个函数时必须为空。
• FPU 控制字在跨函数调用时必须被保护。
• 浮点返回值被返回在 FPU 栈里。即使不使用它们,也需要调用者将它们清除干净。
#10
我这里用ebx, 是因为memcpy 没有用到ebx
但是调用CopyMem的上下文可能用到,你中间把ebx改了,后果不好说,可能没问题也可能很严重。另外这也与用的编译器有关。
是的, 修改ebx 隐藏的风险很严重.
那我的 CopyMem 在 __declspec(naked) 下怎么高效的写呢? 我可不想重新再将参数压栈出栈.
to: zara
刚才跟踪汇编, 是我错了, 但你没讲得很明白, 可能还是我太菜了
调痈约定相同可以直接jmp直接这样写就可以了
__declspec(naked) void __cdecl CopyMem(void * Dst, const void * Src, size_t Size)
{
__asm jmp dword ptr [memcpy]
}
或者
__declspec(naked) void __stdcall CopyMem(void * Dst, const void * Src, size_t Size)
{
__asm
{
call dword ptr [memcpy]
ret 12
}
}
#11
__declspec(naked) void __stdcall CopyMem(void * Dst, const void * Src, size_t Size)
{
__asm
{
call dword ptr [memcpy]
ret 12
}
}
============================================================================
第二个写错了,貌似没别的好办法。
{
__asm
{
call dword ptr [memcpy]
ret 12
}
}
============================================================================
第二个写错了,貌似没别的好办法。
#12
我记得在编译学里面这种上下文相关的寄存器叫非杂合寄存器
放断某语言的参考文档吧,vc下也是这样我试过
13.1.10.1 寄存器协定
• EAX、ECX、EDX 都是杂合(scratch)寄存器,并且会被函数破坏(destroy) 。
• EBX、ESI、EDI、EBP 在跨函数调用时都需要被保护。
• EFLAGS 在跨函数调用时被假定破坏了,除了值必须为向前的方向标志以外。
• FPU 栈在调用一个函数时必须为空。
• FPU 控制字在跨函数调用时必须被保护。
• 浮点返回值被返回在 FPU 栈里。即使不使用它们,也需要调用者将它们清除干净。
非常感谢! 这个跟我的测试不谋而合.
能说一下这是什么手册? 这样我少走一些弯路.
关于dll中调用cdecl函数然后公开(即达到转发器的效果). 最佳代码, 其实很简单.创建一个(静态)模块级变量, 用于保存EIP的执行指令, 然后4句代码.
1, pop 静态变量
2, call cdecl函数
4, push 静态变量
5, ret x
如果cdecl函数的参数就一两个直接push 参数好了, 如果参数在两个以上, 当然用这个, 即方便又快.
#13
我记得在编译学里面这种上下文相关的寄存器叫非杂合寄存器
放断某语言的参考文档吧,vc下也是这样我试过
13.1.10.1 寄存器协定
• EAX、ECX、EDX 都是杂合(scratch)寄存器,并且会被函数破坏(destroy) 。
• EBX、ESI、EDI、EBP 在跨函数调用时都需要被保护。
• EFLAGS 在跨函数调用时被假定破坏了,除了值必须为向前的方向标志以外。
• FPU 栈在调用一个函数时必须为空。
• FPU 控制字在跨函数调用时必须被保护。
• 浮点返回值被返回在 FPU 栈里。即使不使用它们,也需要调用者将它们清除干净。
非常感谢! 这个跟我的测试不谋而合.
能说一下这是什么手册? 这样我少走一些弯路.
关于dll中调用cdecl函数然后公开(即达到转发器的效果). 最佳代码, 其实很简单.创建一个(静态)模块级变量, 用于保存EIP的执行指令, 然后4句代码.
1, pop 静态变量
2, call cdecl函数
4, push 静态变量
5, ret x
如果cdecl函数的参数就一两个直接push 参数好了, 如果参数在两个以上, 当然用这个, 即方便又快.
附图
#14
创建时间: 2016-01-13, 最后更新: 2016-01-13, 来源: 原创/翻译 版本: V1.0
说明备注: 全新翻译;
参考链接: https://msdn.microsoft.com/zh-cn/library/k1a8ss06(v=vs.100).aspx
一般情况下,__asm区块开始时,您不应该假设寄存器将会有指定的值。寄存器值不一定会保留至不同的__asm区块。如果您结束一个在线码区块,并开始另一个,第二个区块寄存器不能依赖第一个区块保留的值。 __asm区块会继承正常控制流的寄存器值结果。
如果您使用__fastcall调用约定时,编译器在寄存器中传递函数参数,而不是在堆栈上。这可能会导致函数中创建 __asm 块出现问题,因为函数没有办法告诉哪个参数是哪个寄存器中。如果函数已经有在 EAX 接收一个参数,但又立即将其他部分存储在 EAX 中的,那么原始参数将会丢失。此外,任何 __fastcall 声明的函数,必须保留 ECX 寄存器.
若要避免此类寄存器的冲突,请不要使用 __fastcall 调用约定的函数去包含一个 __asm 块。如果使用 /Gr 编译器选项来指定默认的调用约定为 __fastcall ,那么函数声明必须使用 __cdecl 或 __stdcall 才能包含 __asm块(__cdecl 特性通知编译器为该函数使用 C 调用约定)。 如果您不使用 /Gr 编译,避免声明函数时使用 __fastcall 特性。
当使用 __asm 在 C/C++ 函数中编写汇编语言,您不需要保留 EAX、 EBX、 ECX、 EDX,ESI 或 EDI 寄存器。例如,示例 POWER2 (在“使用在线汇编编写函数”中)。power2 函数并不会保留在 EAX 寄存器中的值。但是,使用这些寄存器会影响代码质量,因为寄存器分配器就不能使用它们跨 __asm 块来存储值。此外,在在线汇编代码中使用 EBX、 ESI 或 EDI,你必须在函数 prologue(入口) 与 epilogue(出口),强制编译器来保存和还原这些寄存器。
__asm块中,应该保留对其他寄存器的使用 (例如 DS、 SS、 SP、 BP、 和标志寄存器) 。除非您有一些理由更改它们 (如切换堆栈),您应该保留 ESP 和 EBP 寄存器。请参阅“优化汇编代码”。
某些 SSE 类型需要堆栈 8 字节对齐方式,强制编译器将发出动态堆栈对齐方式的代码。只有在对齐方式之后,才能存取局部变量和函数参数,编译器会维护两个框架指针(ESP,EBP)。如果编译器执行框架指针省略 (FPO:frame pointer omission),它将会使用 EBP 和 ESP。如果编译器没有执行 FPO,它将会使用 EBX 和 EBP。如果函数需要动态堆栈对齐,为确保代码运行正常,在asm 代码中不要修改 EBX,因为它可能用来修改框架指针。要么函数中没有移动 8 个字节对齐的类型,要么避免使用 EBX。
注意
如果在线汇编代码使用指令 STD 或 CLD 修改了方向标志,则必须还原标志的原始值。
特意结个贴!
说明备注: 全新翻译;
参考链接: https://msdn.microsoft.com/zh-cn/library/k1a8ss06(v=vs.100).aspx
一般情况下,__asm区块开始时,您不应该假设寄存器将会有指定的值。寄存器值不一定会保留至不同的__asm区块。如果您结束一个在线码区块,并开始另一个,第二个区块寄存器不能依赖第一个区块保留的值。 __asm区块会继承正常控制流的寄存器值结果。
如果您使用__fastcall调用约定时,编译器在寄存器中传递函数参数,而不是在堆栈上。这可能会导致函数中创建 __asm 块出现问题,因为函数没有办法告诉哪个参数是哪个寄存器中。如果函数已经有在 EAX 接收一个参数,但又立即将其他部分存储在 EAX 中的,那么原始参数将会丢失。此外,任何 __fastcall 声明的函数,必须保留 ECX 寄存器.
若要避免此类寄存器的冲突,请不要使用 __fastcall 调用约定的函数去包含一个 __asm 块。如果使用 /Gr 编译器选项来指定默认的调用约定为 __fastcall ,那么函数声明必须使用 __cdecl 或 __stdcall 才能包含 __asm块(__cdecl 特性通知编译器为该函数使用 C 调用约定)。 如果您不使用 /Gr 编译,避免声明函数时使用 __fastcall 特性。
当使用 __asm 在 C/C++ 函数中编写汇编语言,您不需要保留 EAX、 EBX、 ECX、 EDX,ESI 或 EDI 寄存器。例如,示例 POWER2 (在“使用在线汇编编写函数”中)。power2 函数并不会保留在 EAX 寄存器中的值。但是,使用这些寄存器会影响代码质量,因为寄存器分配器就不能使用它们跨 __asm 块来存储值。此外,在在线汇编代码中使用 EBX、 ESI 或 EDI,你必须在函数 prologue(入口) 与 epilogue(出口),强制编译器来保存和还原这些寄存器。
__asm块中,应该保留对其他寄存器的使用 (例如 DS、 SS、 SP、 BP、 和标志寄存器) 。除非您有一些理由更改它们 (如切换堆栈),您应该保留 ESP 和 EBP 寄存器。请参阅“优化汇编代码”。
某些 SSE 类型需要堆栈 8 字节对齐方式,强制编译器将发出动态堆栈对齐方式的代码。只有在对齐方式之后,才能存取局部变量和函数参数,编译器会维护两个框架指针(ESP,EBP)。如果编译器执行框架指针省略 (FPO:frame pointer omission),它将会使用 EBP 和 ESP。如果编译器没有执行 FPO,它将会使用 EBX 和 EBP。如果函数需要动态堆栈对齐,为确保代码运行正常,在asm 代码中不要修改 EBX,因为它可能用来修改框架指针。要么函数中没有移动 8 个字节对齐的类型,要么避免使用 EBX。
注意
如果在线汇编代码使用指令 STD 或 CLD 修改了方向标志,则必须还原标志的原始值。
特意结个贴!