首页 > 解决方案 > 返回两次的 C 函数。(甚至与 fork() 几乎不相似)

问题描述

所以我在做一个项目,有点无聊,想着如何真正难以破解 C: 是否有可能欺骗编译器使用跳转(goto)进行函数调用?-也许,我回答自己。因此,经过一番工作和实践后,我意识到,一些指针的东西不能正常工作,但以一种(至少对我而言)意想不到的方式:goto 无法按预期工作。经过一些试验,我想出了这个东西(注释被删除,因为我有时会在其中保留未使用的代码,在测试时):

//author: me, The Array :)

#include <stdio.h>

void * func_return();
void (*break_ptr)(void) = (void *)func_return;

void * func_return(){
    printf("ok2\n");
    break_ptr = &&test2;
    return NULL;
    if(0 == 1){
      test2:
      printf("sh*t\n");
    }
}

void scoping(){
    printf("beginning of scoping\n");
    break_ptr();
    printf("after func call #1\n");
    break_ptr();
    printf("!!!YOU WILL NOT SEE THIS!!!!\n");
}

int main(){
    printf("beginning of programm\n");
    scoping();
    printf("ending programm\n");
}

我使用 gcc 来编译它,因为我不知道任何其他支持使用 && 的编译器!我的平台是 Windows 64 位,我使用最基本的方法来编译它:

gcc.exe "however_you_want_to_call_it.c" -o "however_you_want_to_call_it.exe"

在查看该代码时,我希望它能够将“sh*t\n”打印到控制台窗口(当然,\n 将是不可见的)。但事实证明 gcc 对我来说有点太聪明了?我想这是在试图破坏某些东西时出现的。事实上,正如标题所说,它返回两次:

beginning of programm
beginning of scoping
ok2
after func call #1
ok2
ending programm

它不会像 fork 函数那样返回两次,并且可能会打印两次以下内容,不,它会从函数和调用它的函数中返回。因此,在第二次调用之后,它不会在控制台打印“!!!你不会看到这个!!!!\n”,而是“结束程序”,因为它返回了两次。(我试图放大事实,即打印“结束程序”,因为程序不会崩溃)

所以,为什么我在这里发布它的原因如下:我的问题..

  1. 为什么它不去/跳转到/调用实际的 test2 标签而是去那个函数的开头?
  2. 我将如何实现我的第一个问题?
  3. 为什么会返回两次?我认为它可能是编译器而不是运行时的东西,但我想我会等待某人的回答
  4. 第一次调用函数“break_ptr”而不是第二次可以实现相同的事情(返回两次)吗?

我不知道也不关心这是否也适用于 c++。

现在我可以看到很多有用的方法,有些是恶意的,有些实际上是好的。例如,您能否编写一个返回您的函数的企业函数。企业对问题的解决方案往往很奇怪,所以为什么不创建一个返回代码的函数,idk.. 然而,它可能是恶意的,例如,当某些代码意外返回或什至没有返回值时......我可以想象这存在在一个 dll 文件和一个简单地读取“extern void *break_ptr();”的头文件中 或者……没有测试它。(然而,还有更残酷的方式来惹某人......)

我在互联网上的任何地方都找不到此文档。请给我一些关于此的链接或参考,如果您找到一些,我想了解更多。

如果这“只是”一个错误并且 gnu/gcc 的某个人正在阅读此内容:请不要删除它,因为使用这些东西太有趣了。

提前感谢您的回答和您的时间,我很抱歉这么久。我想确保收集到的所有内容都集中在一个地方。(但如果我错过了什么,我仍然很抱歉..)

标签: c

解决方案


来自关于值标签的gcc 文档:

您不能使用此机制跳转到不同函数中的代码。如果你这样做,就会发生完全不可预测的事情。

您看到的行为已正确记录。检查生成的程序集以真正了解编译器生成的代码。

gcc10.2上的godbolt程序集没有优化:

break_ptr:
        .quad   func_return
.LC0:
        .string "ok2"
func_return:
        push    rbp
        mov     rbp, rsp
.L2:
        mov     edi, OFFSET FLAT:.LC0
        call    puts
        mov     eax, OFFSET FLAT:.L2
        mov     QWORD PTR break_ptr[rip], rax
        mov     eax, 0
        pop     rbp
        ret
.LC1:
        .string "beginning of scoping"
.LC2:
        .string "after func call #1"
.LC3:
        .string "!!!YOU WILL NOT SEE THIS!!!!"
scoping:
        push    rbp
        mov     rbp, rsp
        mov     edi, OFFSET FLAT:.LC1
        call    puts
        mov     rax, QWORD PTR break_ptr[rip]
        call    rax
        mov     edi, OFFSET FLAT:.LC2
        call    puts
        mov     rax, QWORD PTR break_ptr[rip]
        call    rax
        mov     edi, OFFSET FLAT:.LC3
        call    puts
        nop
        pop     rbp
        ret
.LC4:
        .string "beginning of programm"
.LC5:
        .string "ending programm"
main:
        push    rbp
        mov     rbp, rsp
        mov     edi, OFFSET FLAT:.LC4
        call    puts
        mov     eax, 0
        call    scoping
        mov     edi, OFFSET FLAT:.LC5
        call    puts
        mov     eax, 0
        pop     rbp
        ret

表明.L2label 被放置在 function 之上,并且if (0 == 1) { /* this */ }被编译器优化了。当您跳转时,.L2您会跳转到函数的开头,除了堆栈设置不正确,因为push rbp被省略了。


推荐阅读