C中是否有类似于汇编代码的操作?

时间:2022-06-03 03:15:49

Today, I played around with incrementing function pointers in assembly code to create alternate entry points to a function:

今天,我在汇编代码中使用递增函数指针来创建函数的备用入口点:

.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

(I know, technically this code is invalid since it calls puts without the CRT being initialized, but it works without any assembly or runtime errors, at least on MSVC 2010 SP1.)

(我知道,从技术上讲,这个代码是无效的,因为它在没有初始化CRT的情况下调用puts,但它在没有任何程序集或运行时错误的情况下工作,至少在MSVC 2010 SP1上。)

Note that in the second call to dentry I took the address of the function in the edx register, as before, but this time I incremented it by 13 bytes before calling the function.

请注意,在第二次dentry调用中,我像以前一样在edx寄存器中获取了函数的地址,但是这次我在调用函数之前将它增加了13个字节。

The output of this program is therefore:

因此,该计划的输出是:

C:\Temp>dblentry
Hello!
World!
World!

C:\Temp>

The first output of "Hello!\nWorld!" is from the call to the very beginning of the function, whereas the second output is from the call starting at the "push offset WLD" instruction.

“Hello!\ nWorld!”的第一个输出。从调用到函数的最开始,而第二个输出来自从“推送偏移WLD”指令开始的调用。

I'm wondering if this kind of thing exists in languages that are meant to be a step up from assembler like C, Pascal or FORTRAN. I know C doesn't let you increment function pointers but is there some other way to achieve this kind of thing?

我想知道这种事情是否存在于语言中,这些语言意味着像C,Pascal或FORTRAN这样的汇编程序。我知道C不会让你增加函数指针但是还有其他方法来实现这种事情吗?

2 个解决方案

#1


1  

You can use the longjmp function: http://www.cplusplus.com/reference/csetjmp/longjmp/

您可以使用longjmp功能:http://www.cplusplus.com/reference/csetjmp/longjmp/

It's a fairly horrible function, but it'll do what you seek.

这是一个相当可怕的功能,但它会做你想要的。

#2


1  

AFAIK you can only write functions with multiple entry-points in asm.

AFAIK只能在asm中编写具有多个入口点的函数。

You can put labels on all the entry points, so you can use normal direct calls instead of hard-coding the offsets from the first function-name.

您可以在所有入口点上放置标签,这样您就可以使用常规直接调用,而不是对第一个函数名称的偏移进行硬编码。

This makes it easy to call them normally from C or any other language.

这使得从C或任何其他语言正常调用它们变得容易。

The earlier entry points work like functions that fall-through into the body of another function, if you're worried about confusing tools (or humans) that don't allow function bodies to overlap.

如果您担心混淆不允许函数体重叠的工具(或人类),则较早的入口点就像落入另一个函数体中的函数一样工作。


You might do this if the early entry-points do a tiny bit of extra stuff, and then fall through into the main function. It's mainly going to be a code-size saving technique (which might improve I-cache / uop-cache hit rate).

如果早期的入口点做了一些额外的东西,然后进入主函数,你可能会这样做。它主要是一种代码大小保存技术(可能会提高I-cache / uop-cache命中率)。


Compilers tend to duplicate code between functions instead of sharing large chunks of common implementation between slightly different functions.

编译器倾向于在函数之间复制代码,而不是在稍微不同的函数之间共享大块的常见实现。

However, you can probably accomplish it with only one extra jmp with something like:

但是,你可以用一个额外的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 */ }

See a real example on the Godbolt compiler explorer

查看Godbolt编译器资源管理器的一个真实示例

foo and bar tailcall bigfunc, which is slightly worse than having bar fall-through into bigfunc. (Having foo jump over bar into bigfunc is still good, esp. if bar isn't that trivial.)

foo和bar tailcall bigfunc,这比把棒子掉进bigfunc要差一些。 (让foo跳过bar进入bigfunc仍然很好,特别是如果吧不是那么微不足道。)


Jumping into the middle of a function isn't in general safe, because non-trivial functions usually need to save/restore some regs. So the prologue pushes them, and the epilogue pops them. If you jump into the middle, then the pops in the prologue will unbalance the stack. (i.e. pop off the return address into a register, and return to a garbage address).

跳到函数的中间通常不安全,因为非平凡的函数通常需要保存/恢复一些注册表。所以序幕推动了他们,结语弹出他们。如果你跳到中间,那么序幕中的弹出将使堆栈失衡。 (即将返回地址弹出到寄存器中,并返回垃圾地址)。

See also Does a function with instructions before the entry-point label cause problems for anything (linking)?

另请参阅入口点标签之前带有说明的功能是否会导致任何问题(链接)?

#1


1  

You can use the longjmp function: http://www.cplusplus.com/reference/csetjmp/longjmp/

您可以使用longjmp功能:http://www.cplusplus.com/reference/csetjmp/longjmp/

It's a fairly horrible function, but it'll do what you seek.

这是一个相当可怕的功能,但它会做你想要的。

#2


1  

AFAIK you can only write functions with multiple entry-points in asm.

AFAIK只能在asm中编写具有多个入口点的函数。

You can put labels on all the entry points, so you can use normal direct calls instead of hard-coding the offsets from the first function-name.

您可以在所有入口点上放置标签,这样您就可以使用常规直接调用,而不是对第一个函数名称的偏移进行硬编码。

This makes it easy to call them normally from C or any other language.

这使得从C或任何其他语言正常调用它们变得容易。

The earlier entry points work like functions that fall-through into the body of another function, if you're worried about confusing tools (or humans) that don't allow function bodies to overlap.

如果您担心混淆不允许函数体重叠的工具(或人类),则较早的入口点就像落入另一个函数体中的函数一样工作。


You might do this if the early entry-points do a tiny bit of extra stuff, and then fall through into the main function. It's mainly going to be a code-size saving technique (which might improve I-cache / uop-cache hit rate).

如果早期的入口点做了一些额外的东西,然后进入主函数,你可能会这样做。它主要是一种代码大小保存技术(可能会提高I-cache / uop-cache命中率)。


Compilers tend to duplicate code between functions instead of sharing large chunks of common implementation between slightly different functions.

编译器倾向于在函数之间复制代码,而不是在稍微不同的函数之间共享大块的常见实现。

However, you can probably accomplish it with only one extra jmp with something like:

但是,你可以用一个额外的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 */ }

See a real example on the Godbolt compiler explorer

查看Godbolt编译器资源管理器的一个真实示例

foo and bar tailcall bigfunc, which is slightly worse than having bar fall-through into bigfunc. (Having foo jump over bar into bigfunc is still good, esp. if bar isn't that trivial.)

foo和bar tailcall bigfunc,这比把棒子掉进bigfunc要差一些。 (让foo跳过bar进入bigfunc仍然很好,特别是如果吧不是那么微不足道。)


Jumping into the middle of a function isn't in general safe, because non-trivial functions usually need to save/restore some regs. So the prologue pushes them, and the epilogue pops them. If you jump into the middle, then the pops in the prologue will unbalance the stack. (i.e. pop off the return address into a register, and return to a garbage address).

跳到函数的中间通常不安全,因为非平凡的函数通常需要保存/恢复一些注册表。所以序幕推动了他们,结语弹出他们。如果你跳到中间,那么序幕中的弹出将使堆栈失衡。 (即将返回地址弹出到寄存器中,并返回垃圾地址)。

See also Does a function with instructions before the entry-point label cause problems for anything (linking)?

另请参阅入口点标签之前带有说明的功能是否会导致任何问题(链接)?