[一些好玩的事情] Kernel Panic 的真相和一份实验报告 -- 致 6 年前的自己

1 前言

Just for fun, won't be as big and professional as GNU. -- Linus Torvalds

2020 年的美好的一天.

当时是疫情, 我正在悠哉悠哉地上着学校的网课(摸鱼bushi). 正好当时我着迷黑苹果, 于是就打算在我家那台电脑上试试.

不出所料 KP 了(当时还不知道啥叫 KP), 无论是在物理机还是 VMWare.

忘了说了, 当时我家那台电脑的 CPUIntel Pentium G4400.

然后绝望的我就在百度知道上发了几条帖子.

3

1

后面经过我的不懈努力, 也是终于找到了一个方法 -- 仿冒 CPUID, 也就是图中我圈起来的部分.

但是这个问题并没有消失, 而是一直存在了我的肚子里 -- 为什么加入仿冒 CPUID 后, 它就可以正常工作?

几年之后, 我也算成为了半个业余内核开发者, 至少能看懂一些基本的错误日志.

然后, 最近我又翻到了这篇帖子. 在看了一下这张 KP 图后, 一看堆栈, 我就感觉不太对劲...

4

RAX: 0x00007ffeefa04a70, RBX: 0x00000000ffffffe4, RCX: 0x0000000000000048, RDX: 0x0000000000000020
RSP: 0x00007ffeefa048e0, RBP: 0x00007ffeefa048e0, RSI: 0x00007ffeefa04928, RDI: 0x00007ffeefa04a70
R8:  0x0000000000000403, R9:  0x0000000000000000, R10: 0x0000000000000140, R11: 0x0000000000000148
R12: 0x0000000000000008, R13: 0x00007ffeefa04b58, R14: 0x00007ffeefa04a70, R15: 0x00007ffeefa04acc
RFL: 0x0000000000010202, RIP: 0x0000000101079d0d, CS: 0x000000000000002b, SS: 0x0000000000000023

这不对啊, 这 rsp 怎么是 0x00007ff 开头? 还有这诡异的 CS, 0x2b? 用户态???

这压根就不是因为内核态或者驱动程序崩溃而引起的 KP, 而是一个用户程序! (亏我当时还疯狂地换 Lilu.kext, WhateverGreen.kext...)

那正常的用户态程序挂了就挂了, 发个 SIGSEGV 就好了呀, 凭啥这个用户程序挂掉能直接让整个内核 KP 呢? 答案可能只有一个 -- launchd(换句话说, init).

那这到底是怎么回事? 为什么会出现这种情况呢?

2 问题推测

以下有几个线索, 供我进行初步的原因推断:

  1. 在低于等于 OS X 10.10 上的系统中不会出现这个 Kernel Panic, VMWare 上能正常运行这个系统, 物理机上则会报 Still Waiting For Root Device(当然, 那是另外一个故事了), 只有 EI Capitan 以上的系统会出现.
    • 补充: OS X 10.10 发布年份是 2014, 那个时候最新的硬件是 Broadwell, OS X 10.11 发布时间是 2015, 这个版本开始支持 Skylake.
  2. 在我家的另一台装有 H61 + Celeron G2030 的电脑, 安装任意版本的 macOS 的时候都不会出现这个 Kernel Panic.
  3. 加入 306A0(Ivy Bridge) 的仿冒 CPUID 后, 系统就能正常启动.

因此我认为问题的根源不在于 Apple 恶意 ban 了 PentiumCeleron, 更像是一个意外.

初步推测出问题的调用链

//macOS 内核初始化阶段:
mov eax, 1
cpuid
switch(eax) {
   case 0x506E3:
        AVX=1; SSE42=1;...
   case 0x306A0:
        AVX=0; SSE42=1; ...
} // 压根没管 ECX 和 EDX, 直接对 CPU Capabilities 进行 Hardcode.
//macOS launchd 准备阶段:
if(AVX)
    avx_unzip(...)
//Ivy Bridge 不存在 AVX 相关的指令集, 不会走对应的 avx_unzip.
//Skylake 在 XNU 的视角中应该是存在 AVX 指令集的(毕竟硬编码了).
//但是 G4400 是个特例, 偏偏不存在 AVX.
//-> 执行相关的 AVX 指令后, 系统直接 #UD
//-> launchd 被 XNU kill
//-> Aiee, attempt to kill init!
//-> Kernel Panic
//-> 若仿冒成 Ivy Bridge, 则不会走 AVX 路径.

//正常的系统初始化:
mov eax, 1
cpuid
avx = ((ECX >> 28)&1)

3 实验环境

虽然这张图并没有给我们 macOS 的具体版本号(万恶的 Not yet set), 但是我们可以通过下面的 Kernel Version 反推出来.

Darwin Kernel Version 18.7.0: Thu Jun 20 18:42:21 PDT 2019; root:xnu-4903.270.47~4/RELEASE_X86_64

上网搜了一下, 这个 Kernel 大概对应着这个版本

macOS Version: 10.14.6 
Build: 18G84 或者 18G87

这台机子的主板我们也可以通过 KP 最底下的日志看到, 是微星的 MS-7996, H110M PRO-VD. 这个板子之前是给我用的, 后面被我刷了新的 BIOS, 然后换了个 i3-9100F 给我爸用. 我只记得当时改 BIOS 的时候用 CoffeeTime 删了几个微码, 但忘了删掉了哪几个, 所以我还不太确定这个板子能不能装 G4400 这个U(不过那是后话了).

不过还好, 我家里还有一块闲置的 H610 + 12100F, 刚好趁着这个机会给我爸升级一下配件(其实是为了方便给我弟打游戏, 不对这不能说), 然后那个主板拿来给我做研究. 这样我只需要花 10 块钱就能买到 G4400, 就可以开始进行实验 (双赢这一块算是被我玩明白了bushi)

话不多说, 开整!

实验环境

  • 主板: H110M PRO-VD/VMWare
  • CPU: Intel Pentium G4400
  • VMWare 版本: 14
  • 宿主机系统: Windows 10 20H2
  • 虚拟机系统: macOS 10.14.6 (Build 18G84)
  • 引导: VMWare + unlocker 4.2.6
  • 引导参数: -v debug=0x100

KDK 下载地址: 点击下载
Unlocker 4.2.6 下载地址: jb51

4 开始实验

4.1 问题的复现和猜想的验证

首先我们先屏蔽 AVX 位, 在 vmx 文件末尾屏蔽 CPUID Leaf 1ECXAVX 位和 CPUID Leaf 7AVX2 位 (Bit 5):

cpuid.1.ecx="---0:----:----:----:----:----:----:----"
cpuid.7.ebx="----:----:----:----:----:----:--0-:----"

接下来, 分别设置实验组和对照组的 FakeCPUID

cpuid.1.eax="0000:0000:0000:0011:0000:0110:1010:0000” #0306A0
cpuid.1.eax="0000:0000:0000:0101:0000:0110:1110:0011" #0506E3

实验结果如图所示:

1
在 FakeCPUID 为 0x0506E3 (Skylake) 的时候, 会出现这个 Kernel Panic.
2
在 FakeCPUID 为 0x0306A0 (Ivy Bridge) 的时候, 反而不会出现这个 Kernel Panic, 系统能正常启动.

同时, 我也通过录像, 录到了 Panic 的上半部分:
3

panic(cpu 1 caller 0xffffff880a6bb8ef): initproc exited -- exit reason namespace 2 subcode 0x4

经资料查阅, subcode = 0x4 代表 SIGILL, 也就是 UD.

猜想被证实了, 确实是因为 SIGILL 导致 launchd 挂掉, 最终 Attempt to kill init!.

那么, 到底是哪一行代码导致的 UD 呢? 进入 4.2 节 -- 翻调用栈.

4.2 判断 KP 的原因

首先我们得明确一下这个 Kernel Panic 的结构. 这是我从 VM 上截取的 KP 图:

5

上面的 uuid = 代表加载的 dylib 的信息. 其中:

  • addr 代表 dylib 被加载到的位置
  • uuid 代表 dylib 的加载文件

下面的 Thread 0 类似 Linux 的 Call Stack

但是第一个很明显是内核地址(ffff开头的), 估计是跟 task_kill 有关的函数, 这儿不深究.

所以第二行才是我们想要的用户地址: 0x110837d0d.

根据上面的 uuid 对应的表格来看, 可以看到距离这个地址最近的 dylib 的位置为:

  • 0x110836000 uuid <9d1fe5e4-eb7d-3b3f-a8d1-a96d9cf1348c>

接下来我们可以得到这些信息:

  • UUID=9d1fe5e4-eb7d-3b3f-a8d1-a96d9cf1348c 对应的 dylib 文件就是导致 KP 的真凶.
  • 出问题的指令在 dylib 中的 Offset = 0x110837d0d-0x110836000 = 0x1d0d

挂载 10.14.6BaseSystem.dmg, 然后去 /usr/lib/system 目录下找对应的 .dylib:

6

可以看到对应的文件是 libsystem_platform.dylib, 揪出它, 丢到 Cutter 里面看看是什么情况.

offset 排序, 找到距离 0x1d0d 最近的函数 SA, 最终定位在 0x1cc0 这个位置.
7
Signature, 这是一个针对 Haswell 专门优化的内存移动函数:

void memmove_haswell(unsigned long dst, unsigned long src, unsigned long size);

就是它了! 0x1d0d vmovups xmm1, xmmword [rsi+rdx*1-0x10].

一条典型的 AVX 指令. Pentium G4400 看到这脑子直接宕机: 我 AVX 不是被阉割了吗? 这是什么鬼?

然后就直接一个 #UD 砸下来了, 最终 launchdkill. 问题调用链算是找到了.

9

然后接下来就是对下一层的地址 0x1107ee705 进行 disas, 一样的操作:

  • 找到 uuid=4195838c-efef-3cc9-b459-75032af7eala, offset=0x1705
  • 根据 uuid 找到上层调用 memmove 函数的文件是 libsystem_kernel.dylib
  • Cutter 后定位到 0x1705.

10

emmm... 虽然 0x1705 只是一条人畜无害的 mov 指令, 但是它上面刚好就有一个 _memcpy 啊! 干他!

11

一个间接跳转指令, rax 寻找了两次, 第二次很明显是个 offset, 还有这个全局变量 __platform_string_functions 是什么?

首先顺藤摸瓜找到了一个叫做 libkernel_memmove 的函数. 但这显然和我们之前的 libplatform_memmove 对不上. 这到底是怎么回事?

4.3 探寻 __platform_string_functions 的初始化过程

百思不得其解, 于是我问了一下 ai, 它提示我去看看 libsystemlibplatform 的初始化函数. 这一查还真查出了一点东西.

init 函数有点东西啊, 它传递了一个 __platform_string_functionsrdi 中, 然后调用 __libkernel_platform_init.

12

然后我们再来看一下 __libkernel_platform_init 的实现:

  ;-- func.00001555:
___libkernel_platform_init(int64_t arg1);
; arg int64_t arg1 @ rdi
0x00001555      mov     qword [__libkernel_string_functions], rdi ; 0x2b3f0 ; arg1
; __libkernel_string_functions = rdi(__platform_string_functions)
0x0000155c      xor     eax, eax
0x0000155e      ret

对, 它往__libkernel_string_functions 写入了这个参数!

继续, 我们再看一下 libsystem_string_functions 的这个 __platform_string_functions 是什么.

13

很好, 这个类似的结构体在哪出现过? 对, 上面的 memcpy 中的汇编代码, 我们探究 __libkernel_string_functions 的结构的时候.

  ;-- func.00001816:
void *_memcpy(void *s1, const void *s2, size_t n);
0x00001816      mov     rax, qword [__libkernel_string_functions] ; 0x2b3f0 ; [0x2b3f0] = 0x029010
0x0000181d      mov     rax, qword [rax+0x20] ; 0x99ea
0x00001821      jmp     rax

那么我们就看一下 __platform_string_functions+20 对应的地址是什么:

14

一个 stub, 它名字都写好了, 就叫 memmove!

现在一切都好像有答案了:

  • 这个 __platform_string_functions 是一个存函数指针的结构体, 为了方便针对不同平台进行优化, Apple 采用了这种设计, 方便解耦.
  • 为了防止空指针, __platform_string_functionslibsystem_kernel 的编译期先硬编码为 libkernel 内部的字符串操作函数.
    • 这也解释了我们在反汇编软件看到这个函数的时候, 它指向 libkernel_memmove 而不是 libplatform_memmove 函数的原因
  • libplatform 初始化的时候, 它会重新初始化这个变量, 指向 libplatform 内部的结构体.

4.4 探究 platform_memmove 的实现

好的, 线索到这已经很明朗了, 接下来看看 platform_memmove 的实现.
Ghidra 反编译结果如下:

code * __platform_memmove(void)
{
    if (*(int32_t *)0x7fffffe00040 < 0x573b5eec) {
        if (*(int32_t *)0x7fffffe00040 == 0x1f65e835) {
            return __platform_memmove$VARIANT$Ivybridge;
        }
        if (*(int32_t *)0x7fffffe00040 != 0x5490b78c) {
            return __platform_memmove$VARIANT$Haswell;
        }
    } else if (*(int32_t *)0x7fffffe00040 != 0x573b5eec) {
        if (*(int32_t *)0x7fffffe00040 == 0x78ea4fbc) {
            return __platform_memmove$VARIANT$Base;
        }
        if (*(int32_t *)0x7fffffe00040 != 0x6b5a4cd2) {
            return __platform_memmove$VARIANT$Haswell;
        }
    }
    return __platform_memmove$VARIANT$Nehalem;
}

果然, 硬编码这一块, 实锤了. 这个 memmove 它返回一个函数指针, 所以上面的逻辑闭环了:

mov rax, [rax+20] ;memmove
jmp rax

接下来看看 0x7fffffe00040 这个地址到底藏了什么东西. 查阅资料发现, 这个地址其实是 XNUcommpage 的一部分, 为了实现 zerocopy. 它对应的 offset_COMM_PAGE_CPU_FAMILY.

OK, 翻 XNU 源码.

//osfmk/mach/machine.h
#define CPUFAMILY_INTEL_6_13            0xaa33392b
#define CPUFAMILY_INTEL_PENRYN          0x78ea4fbc
#define CPUFAMILY_INTEL_NEHALEM         0x6b5a4cd2
#define CPUFAMILY_INTEL_WESTMERE        0x573b5eec
#define CPUFAMILY_INTEL_SANDYBRIDGE     0x5490b78c
#define CPUFAMILY_INTEL_IVYBRIDGE       0x1f65e835
#define CPUFAMILY_INTEL_HASWELL         0x10b282dc
#define CPUFAMILY_INTEL_BROADWELL       0x582ed09c
#define CPUFAMILY_INTEL_SKYLAKE         0x37fc219f
#define CPUFAMILY_INTEL_KABYLAKE        0x0f817246
#define CPUFAMILY_INTEL_ICELAKE         0x38435547
#define CPUFAMILY_INTEL_COMETLAKE       0x1cf8a03e

好嘛, 这果然是一堆 magic_number.

然后根据这个头文件搜, 发现了这么一个函数:

static uint32_t
cpuid_set_cpufamily(i386_cpu_info_t *info_p)
{
	uint32_t cpufamily = CPUFAMILY_UNKNOWN;

	switch (info_p->cpuid_family) {
	case 6:
		switch (info_p->cpuid_model) {
		case 23:
			cpufamily = CPUFAMILY_INTEL_PENRYN;
			break;
		case CPUID_MODEL_NEHALEM:
		case CPUID_MODEL_FIELDS:
		case CPUID_MODEL_DALES:
		case CPUID_MODEL_NEHALEM_EX:
			cpufamily = CPUFAMILY_INTEL_NEHALEM;
			break;
		case CPUID_MODEL_DALES_32NM:
		case CPUID_MODEL_WESTMERE:
		case CPUID_MODEL_WESTMERE_EX:
			cpufamily = CPUFAMILY_INTEL_WESTMERE;
			break;
		case CPUID_MODEL_SANDYBRIDGE:
		case CPUID_MODEL_JAKETOWN:
			cpufamily = CPUFAMILY_INTEL_SANDYBRIDGE;
			break;
		case CPUID_MODEL_IVYBRIDGE:
		case CPUID_MODEL_IVYBRIDGE_EP:
			cpufamily = CPUFAMILY_INTEL_IVYBRIDGE;
			break;
		case CPUID_MODEL_HASWELL:
		case CPUID_MODEL_HASWELL_EP:
		case CPUID_MODEL_HASWELL_ULT:
		case CPUID_MODEL_CRYSTALWELL:
			cpufamily = CPUFAMILY_INTEL_HASWELL;
			break;
		case CPUID_MODEL_BROADWELL:
		case CPUID_MODEL_BRYSTALWELL:
			cpufamily = CPUFAMILY_INTEL_BROADWELL;
			break;
		case CPUID_MODEL_SKYLAKE:
		case CPUID_MODEL_SKYLAKE_DT:
		case CPUID_MODEL_SKYLAKE_W:
			cpufamily = CPUFAMILY_INTEL_SKYLAKE;
			break;
		case CPUID_MODEL_KABYLAKE:
		case CPUID_MODEL_KABYLAKE_DT:
			cpufamily = CPUFAMILY_INTEL_KABYLAKE;
			break;
		case CPUID_MODEL_ICELAKE:
		case CPUID_MODEL_ICELAKE_H:
		case CPUID_MODEL_ICELAKE_DT:
			cpufamily = CPUFAMILY_INTEL_ICELAKE;
			break;
		case CPUID_MODEL_COMETLAKE_DT:
			cpufamily = CPUFAMILY_INTEL_COMETLAKE;
			break;
		}
		break;
	}

	info_p->cpuid_cpufamily = cpufamily;
	DBG("cpuid_set_cpufamily(%p) returning 0x%x\n", info_p, cpufamily);
	return cpufamily;
}

好, 继续翻 CPUID_MODEL_* 的宏定义.

//osfmk/i386/cpuid.h
#define CPUID_MODEL_PENRYN              0x17
#define CPUID_MODEL_NEHALEM             0x1A
#define CPUID_MODEL_FIELDS              0x1E    /* Lynnfield, Clarksfield */
#define CPUID_MODEL_DALES               0x1F    /* Havendale, Auburndale */
#define CPUID_MODEL_NEHALEM_EX          0x2E
#define CPUID_MODEL_DALES_32NM          0x25    /* Clarkdale, Arrandale */
#define CPUID_MODEL_WESTMERE            0x2C    /* Gulftown, Westmere-EP/-WS */
#define CPUID_MODEL_WESTMERE_EX         0x2F
//...

然后继续看 i386_cpu_info_t, 找到了这么一个获取 cpuid_info 的函数:

i386_cpu_info_t *
cpuid_info(void)
{
	/* Set-up the cpuid_info stucture lazily */
	if (cpuid_cpu_infop == NULL) {
		PE_parse_boot_argn("-cpuid", &cpuid_dbg, sizeof(cpuid_dbg));
		cpuid_set_info();
		cpuid_cpu_infop = &cpuid_cpu_info;
	}
	return cpuid_cpu_infop;
}

继续看:

void
cpuid_set_info(void)
{
	i386_cpu_info_t         *info_p = &cpuid_cpu_info;
	boolean_t               enable_x86_64h = TRUE;

	/* Perform pre-cpuid workarounds (since their effects impact values returned via cpuid) */
	cpuid_do_precpuid_was();

	cpuid_set_generic_info(info_p); //重点!

	/* verify we are running on a supported CPU */
	if ((strncmp(CPUID_VID_INTEL, info_p->cpuid_vendor,
	    min(strlen(CPUID_STRING_UNKNOWN) + 1,
	    sizeof(info_p->cpuid_vendor)))) ||
	    (cpuid_set_cpufamily(info_p) == CPUFAMILY_UNKNOWN)) {
		panic("Unsupported CPU"); 
          //AMD Hackintosh 玩家表示很淦
     }
     //...
}

static void
cpuid_set_generic_info(i386_cpu_info_t *info_p) {
     	uint32_t        reg[4];
	char            str[128], *p;

	DBG("cpuid_set_generic_info(%p)\n", info_p);

	/* do cpuid 0 to get vendor */
	cpuid_fn(0, reg);
	info_p->cpuid_max_basic = reg[eax];
	bcopy((char *)&reg[ebx], &info_p->cpuid_vendor[0], 4); /* ug */
	bcopy((char *)&reg[ecx], &info_p->cpuid_vendor[8], 4);
	bcopy((char *)&reg[edx], &info_p->cpuid_vendor[4], 4);
	info_p->cpuid_vendor[12] = 0;

	/* get extended cpuid results */
	cpuid_fn(0x80000000, reg);
	info_p->cpuid_max_ext = reg[eax];
     //...
     	wrmsr64(MSR_IA32_BIOS_SIGN_ID, 0);
	cpuid_fn(1, reg);
	info_p->cpuid_microcode_version =
	    (uint32_t) (rdmsr64(MSR_IA32_BIOS_SIGN_ID) >> 32);
	info_p->cpuid_signature = reg[eax]; //演都不演了
	info_p->cpuid_stepping  = bitfield32(reg[eax], 3, 0);
	info_p->cpuid_model     = bitfield32(reg[eax], 7, 4);
	info_p->cpuid_family    = bitfield32(reg[eax], 11, 8);
	info_p->cpuid_type      = bitfield32(reg[eax], 13, 12);
	info_p->cpuid_extmodel  = bitfield32(reg[eax], 19, 16);
	info_p->cpuid_extfamily = bitfield32(reg[eax], 27, 20);
	info_p->cpuid_brand     = bitfield32(reg[ebx], 7, 0);
	info_p->cpuid_features  = quad(reg[ecx], reg[edx]);
     //...
}

然后我们看一下它是怎么写入 commpage 里面的:

//osfmk/i386/cpu_capabilities.h
#define _COMM_PAGE_CPUFAMILY            (_COMM_PAGE_START_ADDRESS+0x040)        /* uint32_t hw.cpufamily, x86*/
//osfmk/i386/commpage/commpage.c
static void
commpage_populate_one(
	vm_map_t        submap,         // commpage32_map or compage64_map
	char **         kernAddressPtr, // &commPagePtr32 or &commPagePtr64
	size_t          area_used,      // _COMM_PAGE32_AREA_USED or _COMM_PAGE64_AREA_USED
	commpage_address_t base_offset, // will become commPageBaseOffset
	commpage_time_data** time_data, // &time_data32 or &time_data64
	new_commpage_timeofday_data_t** gtod_time_data, // &gtod_time_data32 or &gtod_time_data64
	const char*     signature,      // "commpage 32-bit" or "commpage 64-bit"
	vm_prot_t       uperm)
{
     //...
     cfamily = cpuid_info()->cpuid_cpufamily;
	commpage_stuff(_COMM_PAGE_CPUFAMILY, &cfamily, 4);
}

这部分代码负责将 cpufamily 写入 commpage 中.

到此, 实验结束.

5 实验结果

OK, 实锤完毕. 这些代码, 足以证明, 我之前的以下猜想都是正确的.

  • 苹果根据 cpuideax来判断 cpuid_model, cpuid_family.
  • 根据 cpuid_family 来确定 0x7fffffe00040(_COMM_PAGE_CPU_FAMILY) 的值.
  • 到这一步都正常, 但是烂苹果在写 libplatform 的时候, 只根据 _COMM_PAGE_CPU_FAMILY 来判断返回哪一个 memmove 优化函数!
  • 偏偏 G4400AVX 指令集, 一执行, launchd 都直接炸!
  • KP!

但是以下猜想需要修正:

  • 10.11Skylake 在同一年发布G440010.11 及以上的系统才会因为 launchd 崩溃而 KP 这两件事只是一个巧合.
  • __platform_memmove 来看的话, 它压根没有单独添加对于 Skylake 的判断, 而是以 Westmere 架构为分水岭.
    • 如果架构比 Westmere 新:
      • 如果是 Sandy Bridge, 那么走单独的为 Sandy Bridge 优化的处理路线
      • 如果是 Ivy Bridge, 那么走单独的为 NEHALEM 架构的处理路线
      • 否则统一当成有 AVX 指令集的 CPU, 看成 Haswell+.

对于这个结论, 其实只需要尝试在 G4560 上运行 OS X 10.11, 查看实验结果就好:

  • 如果仍然 KP, 那么证明了 KPSkylake 是没有关系的, 是 Apple 对于 10.11 系统的优化太激进.
  • 如果不 KP 了, 那么证明了 Apple 对 Skylake 进行了单独优化, 只是我还没有找到单独优化的函数.

时间和精力有限, 我这儿就不下载 10.1010.11 的镜像去逆向 libplatform.dylib 了, 有兴趣的读者可以自行逆向, 思路应该是差不多的.

于我而言, 我个人倾向于仍然 KP, 因为:

  • OS X 10.11 还不认识 Kaby Lake 的 U
  • 所以 __platform_memmove 的识别结果是 CPU_UNKNOWN
  • 问题是 CPU_UNKNOWN==0, 这照样是比 Westmere 新的 (即小于 0x573b5eec, 无论 signed 比较还是 unsigned)
  • 所以它还是会走 Haswell+ 的优化路径
  • 所以它还是照样会因为没有 AVX 指令集而 KP.
  • 10.10 (Yosemite) 我推测是 Apple 还没开始往操作系统中加入大量的 AVX 指令以优化性能, 所以它能正常运行而不会因为 launchd 崩溃而 KP.

6 感慨

一个牙膏厂,一个烂苹果,一个往死里阉割,一个往死里"忧化",两个神人公司叠加在一起,总之都不把低端奔腾和赛扬的用户当人😷卧龙凤雏了属于是😧😧
只能说受不了一点, 两个神人公司加起来, 组合成的 KP, 给当时 2020 年在上网课的 13 岁的我卡了三个月...
只能说, "强强联手"啊!

还有.

  • 致敬当年的黑苹果时光.
  • 致敬当年"百机齐放"的折腾时光.
  • 致敬 13 岁那个懵懂无知, 但是愿意花 3 个月, 仅仅为了解决一个黑苹果无法启动的, 乐于折腾的我.
  • 成长不是亲手杀死,或者禁闭掉自己的那个内心小孩,而是将内心的小孩和现在的大人进行一次整合,让强大的能力,保护住那个内心的小孩不受伤害。

在这个厂商限制越来越严格的时代, 在这个 BL 锁已经让 Root 和刷机销声匿迹的时代, 在这个黑苹果已经落幕, Apple 全面转向 Apple Silicon 的时代, 愿我们折腾的初心不变.

The End

本期文章写到这, 感谢大家的观看哦~萌新初涉系统编程, 有错误也请多多指正~

版权声明: 本文采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
作者: Sudo-su-Bash (Alien-Bash)
发布时间: 2026-08-01
原文链接: https://www.cnblogs.com/SudosuBash/p/22122804

posted @ 2026-08-01 09:44  SudosuBash  阅读(93)  评论(0)    收藏  举报