【问题标题】:How can I change entry point for a mach-o executable for a c++ file?如何更改 c++ 文件的 mach-o 可执行文件的入口点?
【发布时间】:2018-06-29 21:15:55
【问题描述】:

我正在尝试编写一个没有 main 的 c++ 程序。是否可以将 mach-o 可执行文件的入口点更改为自定义函数(main() 除外)?

如果没有,那么是否可以在调用实际的 C main 之前包装 main 以调用我的 main 版本?

编辑:

我希望我的自定义函数调用 C main。如果我给它一个构造函数属性或将其添加到 ctor 列表中,那么 main 将被调用两次。我不希望这种情况发生。

P.S 我正在使用 clang 版本 9.1.0 在 Mac OS X High Sierra 中构建可执行文件

【问题讨论】:

  • 你不能写int main() { return myfunc(); }?只是好奇...
  • @SeverinPappadeux 我的函数必须在 main 执行之前被调用,这样它应该是我的函数调用 C main 而不是编译器。这就是为什么我问是否有办法更改入口点或创建包装器。
  • 为什么?它有什么不同?
  • 你可以写一个静态类,把你的东西放到ctor里
  • @elvis.dukaj 您可以使用属性将您的函数放入 .init 部分(也在 .fini 中),不必编写代码

标签: c++ clang mach-o


【解决方案1】:

您可以使用 ld 的 -e <symbol> 选项,您可以从 clang 中调用 -Wl,-e,_<symbol>。从历史上看,程序的入口点是 crt0.o 中的 _start,但是自从 Mac OS X 10.8 和 iOS 6.0 引入 LC_MAIN 加载命令(替换 LC_UNIXTHREAD )。 “旧”方式仍然可以使用,但必须使用 -no_new_main 链接器标志显式启用(如果您需要它,它有对应的 -new_main)。 crt0.o 曾经承担的职责已转移到动态链接器/usr/lib/dyld,它可以根据需要同时处理LC_MAINLC_UNIXTHREAD

所以给定一个带有main的C程序:

// t.c
#include <stdio.h>

int main(int argc, const char **argv)
{
    printf("test %i\n", argc);
    return 0;
}

您可以像这样轻松创建 C++ 文件:

// t.cpp

extern int main(int, const char**);

extern "C" int derp(int argc, const char **argv)
{
    return main(0, (const char*[]){ (const char*)0 });
}

然后用clang++ -o t t.cpp -xc t.c -Wl,-e,_derp编译它们。
请务必将derp 声明为extern "C",或在命令行中指定损坏的符号。

您还可以使用otool 检查生成的可执行文件,以确保它使用LC_MAIN 而不是LC_UNIXTHREAD

bash$ otool -l ./t | fgrep -B1 -A3 LC_MAIN
Load command 11
       cmd LC_MAIN
   cmdsize 24
  entryoff 3808
 stacksize 0

【讨论】:

  • 这看起来正是我想要的。你确定我不会失去任何不调用-Wl,-e 命令就可以获得的功能吗?
  • 如果 .init 部分中有函数,它们会被调用吗?否则,对于 c++ 代码作者会感到惊讶
【解决方案2】:

您可以将 -e &lt;symbol name&gt; 选项传递给链接器 (ld) 以指定不同的入口点。默认入口点不是main;它是由 crt1.o 提供的 start,而它又调用 main

【讨论】:

  • 添加-e &lt;symbol name&gt; 意味着我也必须提供nostartfiles。但这会阻止链接到 mac os 的启动文件(如果我错了,请纠正我)。我只打算为 main 函数(或任何调用 main 的函数)创建一个包装器。
  • @Rohit 实际上不,-e 可以按照您的意愿工作。您可以使用derp 而不是main 创建一个小的hello world 二进制文件,使用-Wl,-e,_derp 进行编译,并使用otool -l 检查输出。注意LC_MAIN 的存在,但LC_UNIXTHREAD 的缺失。如果我正确阅读了 ld64 源代码,那么对于 macOS 自 10.8 和 iOS 自 6.0 以来应该是正确的。如果它不适合您,您应该仍然可以使用-Wl,-new_main 强制执行此类行为。 (如果您愿意,也可以使用 -Wl,-no_new_main 强制 start 行为。)
  • 另外,@KenThomases、start 和 crt0.o 已经好几年没有出现在达尔文上了。 LC_MAIN load 命令的出现扼杀了他们,并将这项职责转移到了动态链接器 dyld
  • @KenThomases 手册页是一个非常悲伤的故事,因为 Apple 似乎完全忽略了其中的许多。 ld 的最后一次更新似乎是在 2011 年 - 这确实是在引入 LC_MAIN 之前。 ://
【解决方案3】:

您可以使用_start()

它设置了一些东西,填充参数数组argv,计算有多少参数,然后调用mainmain 返回后,exit 被调用。

这里有几个参考:
https://stackoverflow.com/a/29694977/2302572
http://learningpearls.blogspot.com/2011/02/start-function-inside-c.html

【讨论】:

  • 如果我使用_start() 那么我可以链接到mac os 的libc 吗?
  • 应该是可以的。我不确定 mac os 是否有不同的 gcc 实现。
  • mac os 是否提供类似于__libc_start_main 的东西(来自glibc)?我一般是在linux上工作,需要更改入口点的时候把它包起来。
  • 另外,我使用 clang 作为编译器,而不是 gcc
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多