今天,我在汇编代码中使用递增函数指针来创建函数的备用入口点:
.386
.MODEL FLAT, C
.DATA
INCLUDELIB MSVCRT
EXTRN puts:PROC
HLO DB "Hello!", 0
WLD DB "World!", 0
.CODE
dentry PROC
push offset HLO
call puts
add esp, 4
push offset WLD
call puts
add esp, 4
ret
dentry ENDP
main PROC
lea edx, offset dentry
call edx
lea edx, offset dentry
add edx, 13
call edx
ret
main ENDP
END
(我知道,从技术上讲,这段代码无效,因为它在没有初始化CRT的情况下调用puts
,但它没有任何程序集或运行时错误,至少在MSVC 2010 SP1上是有效的。)
请注意,在第二次调用dentry
时,我像以前一样在edx
寄存器中获取了函数的地址,但是这次我在调用函数之前将它增加了13个字节。
因此该程序的输出为:
C:\Temp>dblentry
Hello!
World!
World!
C:\Temp>
“Hello!\nWorld!
”的第一个输出是从函数的最开始调用,而第二个输出来自从“push offset WLD
”指令开始的调用。
我想知道这种事情是否存在于语言中,这些语言意味着从汇编程序(如C,Pascal或FORTRAN)升级。我知道C不会让你增加函数指针但是还有其他方法来实现这种事情吗?
答案 0 :(得分:1)
您可以使用longjmp函数:http://www.cplusplus.com/reference/csetjmp/longjmp/
这是一个相当可怕的功能,但它会做你想要的。
答案 1 :(得分:1)
AFAIK你只能在asm中编写带有多个入口点的函数。
您可以在所有入口点上放置标签,这样您就可以使用普通的直接调用,而不是对第一个函数名称的偏移进行硬编码。
这样可以很容易地用C或任何其他语言来正常调用它们。
如果您担心不允许函数体重叠的工具(或人类)混乱,那么较早的入口点就像落入另一个函数体内的函数一样工作。
如果早期的入口点做了一些额外的东西,然后进入主函数,你可能会这样做。它主要是一种代码大小保存技术(可能会提高I-cache / uop-cache命中率)。
编译器倾向于在函数之间复制代码,而不是在稍微不同的函数之间共享大块的常见实现。
但是,你可以用一个额外的jmp
完成它,例如:
int foo(int a) { return bigfunc(a + 1); }
int bar(int a) { return bigfunc(a + 2); }
int bigfunc(int x) { /* a lot of code */ }
上的真实示例
foo
和bar
尾调bigfunc
,这比让bar
落入bigfunc
略差。 (将foo
跳过bar
进入bigfunc
仍然很好,尤其是bar
并非如此琐碎。)
跳入函数的中间通常不安全,因为非平凡函数通常需要保存/恢复某些注册表。所以序幕推动了他们,结语弹出他们。如果你跳到中间,那么序言中的pop
将使堆栈失衡。 (即将返回地址弹出到寄存器中,然后返回垃圾地址)。