【问题标题】:Is there any way a C/C++ program can crash before main()?C/C++ 程序有什么方法可以在 main() 之前崩溃吗?
【发布时间】:2011-01-31 21:12:04
【问题描述】:

有什么方法可以在 main() 之前让程序崩溃吗?

【问题讨论】:

  • 我知道我以前见过这个,但那是很久以前的事了,我不确定我是否记得原因。
  • 我可以自信地说,我可以随时导致任何事情崩溃。 :)
  • @Mark Allen:请原谅你名字的拼写错误;还在擦茶!
  • 如果你在运行windows,它可能随时崩溃,没有任何可解释的原因。
  • 是的,当然,因为只有 Windows 会莫名其妙地崩溃。 Linux 世界中根本不存在“内核恐慌”这个术语。

标签: c++ c function crash


【解决方案1】:

使用 gcc,您可以使用 constructor attribute 标记函数(这会导致函数在 main 之前运行)。在下面的函数中,premain会在main之前被调用:

#include <stdio.h>

void premain() __attribute__ ((constructor));

void premain()
{
    fputs("premain\n", stdout);
}

int main()
{
    fputs("main\n", stdout);
    return 0;
}

因此,如果premain 中存在崩溃错误,您将在main 之前崩溃。

【讨论】:

  • 您为什么要这样做?为什么不直接在 main 中调用呢?
  • @Victor:例如,它可以添加到 lib 存档中,这样您就不会看到,初始化一些库部分。
  • 也许如果您的代码在共享库中,并且您不想要求共享库的所有用户在他们的 main() 中调用 premain()(因为反正他们都会忘记做的:))
  • +1 @R Samuel Klatchko:对于这个答案,我已经有用了。
【解决方案2】:

是的,至少在 Windows 下是这样。如果程序使用 DLL,则可以在 main() 启动之前加载它们。这些 DLL 的DllMain 函数将在main() 之前执行。如果他们遇到错误,可能会导致整个进程停止或崩溃。

【讨论】:

  • 如何调试此类问题?
【解决方案3】:

如果你有一个 C++ 程序,它可以在进入 main 之前通过函数和构造函数初始化变量和对象。其中任何一个错误都可能导致程序崩溃。

【讨论】:

【解决方案4】:

简单的答案是:是的

更具体地说,我们可以区分造成这种情况的两个原因。我将它们称为implementation-dependentimplementation-independent

完全不依赖于您的环境的一种情况是 C++ 中的静态对象,这里提到过。以下代码在main() 之前死亡:

#include <iostream>

class Useless {
public:
    Useless() { throw "You can't construct me!"; }

};

static Useless object;

int main() {
    std::cout << "This will never be printed" << std::endl;

    return 0;
}

更有趣的是平台相关的原因。这里提到了一些。这里多次提到的一个是动态链接库的使用(Windows 中的 DLL,Linux 中的 SO 等) - 如果您的操作系统的加载程序在 main() 之前加载它们,它们可能会导致您的应用程序在 @987654324 之前死掉@。

这个原因的一个更一般的版本是在调用你的入口点(main())之前讨论你的二进制入口点所做的所有事情。通常当你构建你的二进制文件时,当你的操作系统的加载器开始运行你的二进制文件时,会调用一个非常重要的代码块,当它完成时它会调用你的main()。这段代码做的一件常见的事情是初始化 C/C++ 标准库。此代码可能因多种原因而失败(尝试为其分配的任何类型的系统资源不足)。

在 Windows 上 main() 之前二进制执行代码的一种有趣方式是使用 TLS 回调(谷歌会告诉你更多关于它们的信息)。这种技术通常作为一种基本的反调试技巧在恶意软件中被发现(这个技巧曾经用来欺骗 ollydbg,不知道现在是否仍然如此)。

关键是你的问题实际上等同于“有没有办法加载二进制文件会导致用户代码在main()中的代码之前执行?”,答案是地狱,是的!

【讨论】:

    【解决方案5】:

    当然在 C++ 中;带有构造函数的静态对象会在 main 之前被调用——它们可能会死

    不确定c

    这里是示例

    class X
    {
    public:
    X()
    {
      char *x = 0;
      *x = 1;
    }
    };
    
    X x;
    int main()
    {
    return 0;
    }
    

    这将在 main 之前崩溃

    【讨论】:

    • 问题是关于 C,而不是 C++。
    • @Thi 然后说出来,使用标签和问题标题和文本!但这是一个关于 C 的好问题,而关于 C++ 的一个不太好的问题,因为在这种情况下的答案是微不足道的——“是”。
    【解决方案6】:

    任何依赖于在 main 之前加载的共享对象 (DLL) 的程序都可能在 main 之前失败。

    在 Linux 下,动态链接器库 (ld-*.so) 中的代码在 main 之前运行以提供任何库依赖项。如果无法找到任何需要的库,具有不允许您访问它们的权限,不是普通文件,或者没有链接您的程序的动态链接器认为它应该具有的某些符号它链接了你的程序,那么这可能会导致失败。

    此外,每个库在链接时都会运行一些代码。这主要是因为库可能需要链接更多库或可能需要运行一些构造函数(即使在 C 程序中,库也可能有一些 C++ 或其他使用构造函数的东西)。 此外,标准 C 程序已经创建了 stdio 文件 stdin、stdout 和 stderr。在许多系统上,这些也可以关闭。这意味着它们也是 free()ed,这意味着它们(和它们的缓冲区)是 malloc()ed,这可能会失败。这也表明他们可能对那些 FILE 结构所代表的文件描述符做了一些其他的事情,这可能会失败。

    其他可能发生的事情可能是操作系统搞砸了设置传递给程序的环境变量和/或命令行参数。 main 之前的代码很可能在调用 main 之前必须处理这些数据。

    在 main 之前发生了很多事情。他们中的任何一个都可能以致命的方式失败。

    【讨论】:

      【解决方案7】:

      我不确定,但如果你有这样的全局变量:

      static SomeClass object;
      
      int main(){
         return 0;
      }
      

      “SomeClass”构造函数可能会在 main 执行之前使程序崩溃。

      【讨论】:

      • 在 C 中很难有一个类构造函数,这个问题被标记了。
      • 问题是关于 C,而不是 C++。
      • 我假设 C++ 的情况,无论如何,如果它只是 C 相关的,我真的不知道。反正没关系,下次多注意tags。
      • @smerlin,事后才添加。
      【解决方案8】:

      有很多可能性。

      首先,我们需要了解在 main 执行之前实际发生了什么:

      • 加载动态库
      • 全局变量的初始化
      • 一些编译器,一些函数可以显式执行

      现在,其中任何一个都可能以多种方式导致崩溃:

      • 通常的未定义行为(取消引用空指针、访问不应访问的内存...)
      • 抛出异常> 因为没有catch,所以调用terminate,程序结束

      这当然很烦人,而且可能很难调试,这就是为什么你应该尽可能避免在main之前执行代码,如果可以的话,更喜欢延迟初始化,或者在main中显式初始化。

      当然,当 DLL 失败并且您无法修改它时,您将陷入痛苦的世界。

      【讨论】:

        【解决方案9】:

        种类: http://blog.ksplice.com/2010/03/libc-free-world/

        如果你在没有标准库的情况下编译,像这样: gcc -nostdlib -o hello hello.c

        它不知道如何运行 main() 并且会崩溃。

        【讨论】:

          【解决方案10】:

          C++ 程序中的全局和静态对象将在 main() 中的第一条语句执行之前调用其构造函数,因此其中一个构造函数中的错误可能会导致崩溃。

          不过,这在 C 程序中不会发生。

          【讨论】:

            【解决方案11】:

            这取决于你所说的“在 main 之前”是什么意思,但如果你的意思是“在 main 中的任何代码实际执行之前”,那么我可以想到一个例子:如果你在 main 中声明一个大数组作为局部变量,并且这个数组的大小超过了可用的堆栈空间,那么你很可能会在第一行代码执行之前在 main 入口处获得一个stack overflow

            【讨论】:

            • 我知道你首先有了数组的想法。但是为什么要本地化呢?只需给它文件范围。文件范围内的char big[-1U / 2U]; 也会导致崩溃。
            • @johannes: 是的,这可能也有效——main 中的本地数组只需要 8 MB 左右,具体取决于您的操作系统的默认堆栈大小,所以它比你的大锤方法用一个巨大的阵列。 ;-)
            【解决方案12】:

            一个有点做作的例子是:

            int a = 1;
            int b = 0;
            int c = a / b;
            
            int main()
            {
                return 0;
            }
            

            你不太可能做过这样的事情,但如果你做了很多宏观魔术,那是完全有可能的。

            【讨论】:

            • gcc 4.4 不会编译这个:t.c:3: error: initializer element is not constant
            • 它在 vs 2005 中编译得很好。gcc 对这种东西更严格,这绝对是一件好事。 :) 我只是要指出,这种行为可以通过巧妙的宏计算等在 C 语言中非常常见的东西产生。
            【解决方案13】:
            class Crash
            {
            public:
              Crash( int* p )
              { *p = 0; }
            };
            
            static Crash static_crash( 0 );
            
            void main()
            {
            }
            

            【讨论】:

              【解决方案14】:

              我也遇到过同样的问题。找到的根本原因是.. 在主进程中初始化了过多的局部变量(巨大的数组),导致局部变量大小超过 1.5 mb。
              这会导致一个大跳转,因为堆栈指针非常大,并且操作系统检测到这个跳转是无效的并且因为它可能是恶意的而导致程序崩溃。

              调试这个。
              1. 启动 GDB
              2.在main处添加断点
              3.反汇编主
              4. 检查低于 $0xGGGGGGGG,%esp
              如果这个 GGGGGG 值太高,你会看到和我一样的问题。

              所以检查 main 中所有局部变量的总大小。

              【讨论】:

              • 解决这个问题的方法是删除巨大的数组初始化并改为 malloc。
              • 有什么办法可以增加使用的内存,以便初始化大数组/
              • 我不确定我们是否可以做到这一点。我认为这可能需要更改内核
              【解决方案15】:

              当然,如果操作系统或运行时代码中存在错误。 C++ 因这种行为而臭名昭著,但它仍然可以在 C 中发生。

              【讨论】:

              • downvote 是不公平的......这是公平的,因为它是一个有效的假设。
              • 虽然我没有投反对票,但我想这是对 C++ 所谓的坏名声的争议性打击。
              • 我猜是这样 - 但 C++ 程序确实一直存在这个问题。
              【解决方案16】:

              你还没有说哪个平台/libc。在嵌入式世界中,经常有很多事情在main() 之前运行——主要与平台设置有关——这可能会出错。 (或者实际上,如果您在常规操作系统上使用时髦的链接器脚本,那么所有的赌注都没有了,但我想这非常罕见。)

              【讨论】:

                【解决方案17】:

                一些平台抽象库覆盖(我个人只知道像 Qt 或 ACE 这样的 C++ 库,它们会这样做,但也许一些 C 库也会这样做)“main”,以便它们指定特定于平台的 main,例如一个int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ); 并设置一些库的东西,将命令行参数转换为普通的int argc, char* argv[],然后调用普通的int main(int argc, char* argv[])

                当然,如果此类库没有正确实现,可能会导致崩溃(可能是命令行参数格式错误的原因)。

                对于不知道这件事的人来说,这可能看起来像是main之前的崩溃

                【讨论】:

                  【解决方案18】:

                  main 之前因堆栈溢出而使程序崩溃的最佳示例:

                  int main() {
                      char volatile stackoverflow[1000000000] = {0};
                      return 0;
                  }
                  

                  【讨论】:

                    猜你喜欢
                    • 2016-02-03
                    • 1970-01-01
                    • 2013-08-08
                    • 2019-03-24
                    • 1970-01-01
                    • 2012-01-18
                    • 2011-04-18
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多