剖析 macOS 上的 gcc 二进制文件

Lobsters Hottest 新闻

摘要

一篇博客深入逆向分析 macOS 上的 `gcc` 二进制文件,揭示它并非简单的 clang 别名,而是通过 libxcselect 的 xcselect_invoke_xcrun() 调用 xcrun,将请求转发到真实的开发者工具。

<p><a href="https://lobste.rs/s/hplfis/examining_macos_gcc_binary">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/10/22 15:57

# 剖析 macOS 上的 gcc 二进制文件 来源: https://zoey-on-github.github.io/blog/macgcc.html 如果你在 Mac 上写过 C(或 C++),你就会知道所谓的 `gcc` 其实就是换了马甲的 clang。 ``` $ gcc clang: error: no input files ``` 大多数人(合理地)认为这只是个别名,但事实并非如此!它是一个二进制程序。那么,这个二进制程序*究竟*是什么?我天真地以为它只是调用 `clang`,但结果远比这有趣。 ```c int sub_1000006c4(int arg0, int arg1) { r19 = arg1; r20 = arg0; if (*_mainExecutableToolName.once == -0x1) { if (*(int8_t *)*_mainExecutableToolName.toolName == 0x63 && *(int8_t *)(*_mainExecutableToolName.toolName + 0x1) == 0x63 && *(int8_t *)(*_mainExecutableToolName.toolName + 0x2) == 0x0) { r21 = "clang"; } else { if (strcmp(*_mainExecutableToolName.toolName, "llvm-gcc") == 0x0) { r21 = "clang"; } else { r21 = *_mainExecutableToolName.toolName; if (strcmp(*_mainExecutableToolName.toolName, "c++") == 0x0 || strcmp(r21, "llvm-g++") == 0x0) { r21 = "clang++"; } } xcselect_invoke_xcrun(); } while (true) { dispatch_once(_mainExecutableToolName.once, ___block_literal_global); if (*(int8_t *)*_mainExecutableToolName.toolName == 0x63) { if (*(int8_t *)(*_mainExecutableToolName.toolName + 0x1) == 0x63) { if (*(int8_t *)(*_mainExecutableToolName.toolName + 0x2) != 0x0) { if (strcmp(*_mainExecutableToolName.toolName, "llvm-gcc") == 0x0) { r21 = "clang"; } else { if (strcmp(*_mainExecutableToolName.toolName, "c++") != 0x0) { r21 = *_mainExecutableToolName.toolName; if (strcmp(*_mainExecutableToolName.toolName, "llvm-g++") == 0x0) { r21 = "clang++"; } } else { r21 = "clang++"; } } xcselect_invoke_xcrun(); } else { xcselect_invoke_xcrun(); } } else { if (strcmp(*_mainExecutableToolName.toolName, "llvm-gcc") == 0x0) { r21 = "clang"; } else { if (strcmp(*_mainExecutableToolName.toolName, "c++") != 0x0) { r21 = *_mainExecutableToolName.toolName; if (strcmp(*_mainExecutableToolName.toolName, "llvm-g++") == 0x0) { r21 = "clang++"; } } else { r21 = "clang++"; } } xcselect_invoke_xcrun(); } } else { if (strcmp(*_mainExecutableToolName.toolName, "llvm-gcc") == 0x0) { r21 = "clang"; } else { if (strcmp(*_mainExecutableToolName.toolName, "c++") != 0x0) { r21 = *_mainExecutableToolName.toolName; if (strcmp(*_mainExecutableToolName.toolName, "llvm-g++") == 0x0) { r21 = "clang++"; } } else { r21 = "clang++"; } } xcselect_invoke_xcrun(); } continue; } } } ``` 这里有一些有趣的地方(比如专门检查是否以 c++ 或 llvm-gcc 方式运行的特殊逻辑),但我们真正关心的是这一行代码:`xcselect_invoke_xcrun()`。这是来自 `libxcselect.dylib` 的一个函数,但它的行为基本等同于运行 `xcrun`。(有趣的是,`xcrun` 二进制程序本身基本上就是调用了这个函数——https://zoey-on-github.github.io/blog/images/Screenshot_2026-10-21_at_11.38.00_PM.png) 如果你反汇编任何通过 Xcode 安装的开发者工具,你会看到完全相同的代码——https://zoey-on-github.github.io/blog/images/Screenshot_2026-10-21_at_12.57.05_PM.png——这意味着 `xcode-select` 在 macOS 上安装的所有开发者工具实际上都是通过 `xcrun` 来运行的。¹ 但这引出了一个重要问题:`xcrun` *到底*是做什么的?在此之前我确实从未听说过它。查看[手册页](https://keith.github.io/xcode-man-pages/xcrun.1.html)后可以看到,它的功能是调用开发者工具目录中的开发者工具。这给了我们一条重要信息:*真正的* gcc 二进制文件的位置。幸运的是,有一个 `--find` 命令可以精确地告诉我们答案。 ``` $ xcrun --find gcc /Library/Developer/CommandLineTools/usr/bin/gcc ``` 查看这个目录,我们可以看到: - `c++`、`llvm-gcc`、`llvm-g++` 并不在这个目录中,这就是为什么它们被硬编码在二进制程序内部 - `cc` 和 `clang++` 只是 `clang` 的别名 但最有趣的是,`g++` 是指向同一目录中一个真正的 gcc 二进制文件的别名。 那么,*那个* gcc 二进制文件又是做什么的呢?用我的反汇编器打开它,我们发现它……调用 `xcrun` 并执行 `clang`(或根据调用方式选择 `clang++`)。 ```c int EntryPoint(int arg0, int arg1) { r22 = arg1; r21 = arg0; asm { sbfiz x8, x21, #0x3, #0x20 }; r0 = malloc(r8 + 0x10); if (r0 == 0x0) { err(0x47, r1); } r19 = r0; *r0 = "/usr/bin/xcrun"; r23 = *r22; r0 = strlen(r23); if (*(int8_t *)(r23 + (r0 - 0x1 << r64)) == 0x2b && *(int8_t *)(r23 + (r0 - 0x2 << r64)) == 0x2b) { r8 = "clang++"; } else { r8 = "clang"; } r19[0x1] = r8; if (r21 >= 0x2) { asm { ubfiz x2, x8, #0x3, #0x20 }; memcpy(r19 + 0x10, r22 + 0x8, r2); r8 = r21; } else { r8 = 0x1; } *(r19 + r8 * 0x8 + 0x8) = 0x0; r0 = execv("/usr/bin/xcrun", r19); err(0x47, r1); return r0; } ``` --- 1. 如果你好奇*为什么会*这样,我的猜测是因为 SIP(系统完整性保护)。`/usr/bin` 在 macOS 上是受保护目录,我猜 Apple 不重启就无法更新它,而更新 Xcode 工具不需要重启 Mac。至于为什么不直接把开发者工具目录添加到 PATH 中,我的猜测是他们希望随时可以更改这个目录的自由。[↩︎](https://zoey-on-github.github.io/blog/macgcc.html#fnref1)

相似文章

@mylifcc: “为什么都 2026 年了,写一个 iOS App 依然不能像写 Rust、Go 甚至 Android 一样,在任意操作系统的终端里一行命令跑起来?” 这个困扰了开发者十多年的问题,终于在开源社区迎来了硬核回应。 由 Kabir Ober…

X AI KOLs Timeline

开源工具 xtool(由 Kabir Oberai 等 13 位贡献者打造,GitHub 已获 11.8k Stars)试图让 iOS 应用开发能像 Rust、Go 或 Android 那样,在任意操作系统的终端里用一行命令构建运行,摆脱对 Xcode 的依赖。

在现代 glibc 上剖析 House of Apple 2

Lobsters Hottest

本文剖析了在现代 glibc 版本上的 House of Apple 2 利用技术,提供了交互式 GDB 演练,并通过沙箱环境分析了其可行性。

xtool-org/xtool

GitHub Trending (daily)

xtool 是一个跨平台(Linux/WSL/macOS)的 Xcode 替代工具,支持使用 SwiftPM 构建、签名和部署 iOS 应用,并可编程方式与 Apple Developer Services 交互。它还提供 XKit 库供开发者在自己的项目中集成。

@xiangxiang103: 卧槽,Anthropic 这次真的翻大车了。 有人在 Claude Code 的二进制里挖出了隐藏代码——专门检测你是不是中国用户、或者代理走了中国。不是普通遥测,是故意混淆、发行说明不写、用户完全不知情的那种。 过程很骚: - 检测到你…

X AI KOLs Timeline

有人在 Claude Code 的二进制中发现隐藏代码,专门检测中国用户或代理,并偷偷修改 system prompt 添加水印,引发对开发者工具信任的广泛质疑。

C++ Insights – 以编译器的视角审视你的源代码

Hacker News Top

C++ Insights 是一个基于 Clang 的开源工具,能够执行 C++ 源码到源码的转换,将编译器背后的“魔法”——如特殊成员函数、lambda 表达式、基于范围的 for 循环以及结构化绑定——直观地呈现出来。它帮助开发者和教育工作者以编译器的视角理解代码,并提供在线托管版本可直接使用。