首页
社区
课程
招聘
[原创]PerspectiveMacos-从xnu memcpy崩溃学习kalloc
发表于: 11小时前 175

[原创]PerspectiveMacos-从xnu memcpy崩溃学习kalloc

11小时前
175


1.重新编译xnu 内核并使用 LLDB 调试崩溃了

堆栈

(lldb) c
Process 1 resuming
Process 1 stopped
* thread #1, stop reason = breakpoint 7.1
    frame #0: 0xffffff8010eda4c0 kernel.debug`panic(str="Kernel trap at 0x%016llx, type %d=%s, registers:\nCR0: 0x%016llx, CR2: 0x%016llx, CR3: 0x%016llx, CR4: 0x%016llx\nRAX: 0x%016llx, RBX: 0x%016llx, RCX: 0x%016llx, RDX: 0x%016llx\nRSP: 0x%016llx, RBP: 0x%016llx, RSI: 0x%016llx, RDI: 0x%016llx\nR8:  0x%016llx, R9:  0x%016llx, R10: 0x%016llx, R11: 0x%016llx\nR12: 0x%016llx, R13: 0x%016llx, R14: 0x%016llx, R15: 0x%016llx\nRFL: 0x%016llx, RIP: 0x%016llx, CS:  0x%016llx, SS:  0x%016llx\nFault CR2: 0x%016llx, Error code: 0x%016llx, Fault CPU: 0x%x%s%s%s%s, PL: %d, VF: %d\n") at debug.c:800:10
   797     void
   798     panic(const char *str, ...)
   799     {
-> 800         va_list panic_str_args;
   801     
   802         va_start(panic_str_args, str);
   803         panic_trap_to_debugger(str, &panic_str_args, 0, NULL, 0, NULL, (unsigned long)(char *)__builtin_return_address(0));
Target 1: (boot.efi) stopped.
(lldb) bt
* thread #1, stop reason = breakpoint 7.1
  * frame #0: 0xffffff8010eda4c0 kernel.debug`panic(str="Kernel trap at 0x%016llx, type %d=%s, registers:\nCR0: 0x%016llx, CR2: 0x%016llx, CR3: 0x%016llx, CR4: 0x%016llx\nRAX: 0x%016llx, RBX: 0x%016llx, RCX: 0x%016llx, RDX: 0x%016llx\nRSP: 0x%016llx, RBP: 0x%016llx, RSI: 0x%016llx, RDI: 0x%016llx\nR8:  0x%016llx, R9:  0x%016llx, R10: 0x%016llx, R11: 0x%016llx\nR12: 0x%016llx, R13: 0x%016llx, R14: 0x%016llx, R15: 0x%016llx\nRFL: 0x%016llx, RIP: 0x%016llx, CS:  0x%016llx, SS:  0x%016llx\nFault CR2: 0x%016llx, Error code: 0x%016llx, Fault CPU: 0x%x%s%s%s%s, PL: %d, VF: %d\n") at debug.c:800:10
    frame #1: 0xffffff8010579062 kernel.debug`panic_trap(regs=0xffffff80101a6400, pl=1, fault_result=0) at trap.c:896:2
    frame #2: 0xffffff80105787cd kernel.debug`kernel_trap(state=0xffffff80101a63f0, lo_spp=0xffffff80101a63d0) at trap.c:834:2
    frame #3: 0xffffff8010598b2f kernel.debug`trap_from_kernel + 38
    frame #4: 0xffffff8010599f55 kernel.debug`counter_inc(counter=0x0000000000000398) at counter.c:77:4
    frame #5: 0xffffff801042fdb2 kernel.debug`vm_fault_internal(map=0xffffff8011ed5100, vaddr=18446743693081014272, caller_prot=3, change_wiring=0, wire_tag=0, interruptible=0, caller_pmap=0x0000000000000000, caller_pmap_addr=0, physpage_p=0x0000000000000000) at vm_fault.c:4008:2
    frame #6: 0xffffff801042f317 kernel.debug`_vm_fault$XNU_INTERNAL(map=0xffffff8011ed5100, vaddr=18446743693081014272, fault_type=3, change_wiring=0, wire_tag=0, interruptible=0, caller_pmap=0x0000000000000000, caller_pmap_addr=0) at vm_fault.c:3725:9
    frame #7: 0xffffff80105786aa kernel.debug`kernel_trap(state=0xffffff80101a6d70, lo_spp=0xffffff80101a6d50) at trap.c:761:27
    frame #8: 0xffffff8010598b2f kernel.debug`trap_from_kernel + 38
    frame #9: 0xffffff8010547307 kernel.debug`memcpy + 7
    frame #10: 0xffffff8010317309 kernel.debug`ledger_entry_add(template=0xffffff934ccadfc0, key="wired_mem", group="physmem", units="bytes") at ledger.c:291:3
    frame #11: 0xffffff8010357cb0 kernel.debug`init_task_ledgers at task.c:1050:27
    frame #12: 0xffffff80102f92b5 kernel.debug`coalitions_init at coalition.c:2066:2
    frame #13: 0xffffff8010351a88 kernel.debug`kernel_bootstrap at startup.c:477:2
    frame #14: 0xffffff8010581ede kernel.debug`machine_startup at model_dep.c:332:2
    frame #15: 0xffffff80105584b4 kernel.debug`i386_init at i386_init.c:1118:2
    frame #16: 0xffffff801056afa8 kernel.debug`x86_init_wrapper + 8
(lldb)
(lldb)  
Process 1 stopped
* thread #1, stop reason = step over
    frame #0: 0xffffff801031729f kernel.debug`ledger_entry_add(template=0xffffff934cd87800, key="wired_mem", group="physmem", units="bytes") at ledger.c:286:17
   283        template_unlock(template);
   284        return -1;
   285      }
-> 286      new_entries = kalloc(new_sz);
   287      if (new_entries == NULL) {
   288        template_unlock(template);
   289        return -1;
Target 1: (boot.efi) stopped.
(lldb) p/x new_sz
(int) 0x00000380
(lldb) n
Process 1 stopped
* thread #1, stop reason = step over
    frame #0: 0xffffff80103172d0 kernel.debug`ledger_entry_add(template=0xffffff934cd87800, key="wired_mem", group="physmem", units="bytes") at ledger.c:287:19
   284        return -1;
   285      }
   286      new_entries = kalloc(new_sz);
-> 287      if (new_entries == NULL) {
   288        template_unlock(template);
   289        return -1;
   290      }
Target 1: (boot.efi) stopped.
(lldb) p/x new_entries
(entry_template *) 0xffffffa019b24000

从崩溃堆栈结合源代码分析可以看出崩溃原因是 kalloc 返回的 0xffffffa019b24000 在进行memcpy 触发 vm_fault_internal 

2.查找触发 vm_fault_internal 原因

考虑是 new_entries = kalloc(new_sz); 返回的 new_entries 虚拟地址没有映射物理地址导致的

ledger_entry_add方法 (src/Kernel/xnu/osfmk/kern/ledger.c)

/*
 * Add a new entry to the list of entries in a ledger template. There is
 * currently no mechanism to remove an entry.  Implementing such a mechanism
 * would require us to maintain per-entry reference counts, which we would
 * prefer to avoid if possible.
 */
/*
 * 中文概要:往 ledger 模板表里追加一条计量项定义(key/分组/单位),返回该条在 lt_entries[]
 * 中的索引,供运行时 ledger_t 与同序号的余额数组对应。-1 表示参数非法或 kalloc 失败。
 * (当前不提供删除条目,以免要维护每条目的引用计数。)
 */
int
ledger_entry_add(ledger_template_t template, const char *key,
    const char *group, const char *units)
{
  /* 成功时设为 template->lt_cnt++ 之前的计数,作为返回给调用方的新条目索引。 */
  int idx;
  /* 指向 lt_entries[idx](即表中「下一条要写」的那一个 entry_template 槽)。 */
  struct entry_template *et;
  /* key 不能为空;长度必须 < LEDGER_NAME_MAX(留尾随 NUL);已关联 zalloc zone 实例的模板禁止再改条目表。 */
  if ((key == NULL) || (strlen(key) >= LEDGER_NAME_MAX) || (template->lt_zone != NULL)) {
    return -1;
  }
  /* 保护 lt_cnt / lt_entries / lt_table_size 与扩容逻辑不被并发破坏。 */
  template_lock(template);
  /* If the table is full, attempt to double its size */
  /* 已用条目数等于当前容量 → 无法再尾插,需把条目表整块翻倍重分配。 */
  if (template->lt_cnt == template->lt_table_size) {
    /* new_entries:新分配的更大数组;old_entries:待 kfree 的旧指针。s:升 IPL 时用。 */
    struct entry_template *new_entries, *old_entries;
    /* 扩容前后的「条目个数」及对应字节长度;new_sz 为翻倍后字节数。 */
    int old_cnt, old_sz, new_sz = 0;
    spl_t s;
    /* 当前表大小(条目数);下面 old_sz / new_sz 均按 struct entry_template 计字节。 */
    old_cnt = template->lt_table_size;
    old_sz = old_cnt * (int)(sizeof(struct entry_template));
    /* double old_sz allocation, but check for overflow */
    /* old_sz * 2 溢出则拒绝扩容避免错误大小 kalloc。 */
    if (os_mul_overflow(old_sz, 2, &new_sz)) {
      template_unlock(template);
      return -1;
    }
    /* 按翻倍后字节数分配新条目表(内容尚未初始化后半段)。 */
    new_entries = kalloc(new_sz);
    if (new_entries == NULL) {
      template_unlock(template);
      return -1;
    }
    /* 把旧表中已有 entry_template 原样拷贝到新表前半(保持已有 key/索引不变)。 */
    memcpy(new_entries, template->lt_entries, old_sz);
    /* 新表后半段清零:对应新增槽位默认为空结构,避免读到垃圾。 */
    memset(((char *)new_entries) + old_sz, 0, old_sz);
    /* assume: if the sz didn't overflow, neither will the count */
    /* 容量(可容纳条目数)翻倍;条目数 lt_cnt 暂不变——随后仍插在 lt_cnt。 */
    template->lt_table_size = old_cnt * 2;
    /* 暂存旧数组指针以便 kfree(在换掉 template->lt_entries 之后)。 */
    old_entries = template->lt_entries;
    /* 升到调度级 IPL 并把 lt_inuse CAS 置 1:与读模板处配合,换掉 lt_entries 指针时避免出现撕裂读。 */
    TEMPLATE_INUSE(s, template);
    template->lt_entries = new_entries;
    TEMPLATE_IDLE(s, template);
    kfree(old_entries, old_sz);
  }
  /* 下一空闲槽等于当前条目个数;拷贝完后再 lt_cnt++。 */
  et = &template->lt_entries[template->lt_cnt];
  /* key / group / units 三组显示名拷贝进模板条目(均被截断在 LEDGER_NAME_MAX 以内)。 */
  strlcpy(et->et_key, key, LEDGER_NAME_MAX);
  strlcpy(et->et_group, group, LEDGER_NAME_MAX);
  strlcpy(et->et_units, units, LEDGER_NAME_MAX);
  /* 新条目默认可记账(未 inactive)。 */
  et->et_flags = LF_ENTRY_ACTIVE;
  /* 暂未挂回调(例如告警时可由运行时再设)。 */
  et->et_callback = NULL;
  /* 对外返回的条目索引即为追加前计数;此后模板内条目数加一。 */
  idx = template->lt_cnt++;
  template_unlock(template);
  return idx;
}

kalloc (src/Kernel/xnu/osfmk/kern/kalloc.h) -> kheap_alloc(src/Kernel/xnu/osfmk/kern/kalloc.h) -> zalloc_ext (src/Kernel/xnu/osfmk/kern/zalloc.c) -> zalloc_gz (src/Kernel/xnu/osfmk/kern/zalloc.c) -> zalloc_return (src/Kernel/xnu/osfmk/kern/zalloc.c) -> zone_element_addr (src/Kernel/xnu/osfmk/kern/zalloc.c)

kalloc (src/Kernel/xnu/osfmk/kern/kalloc.h)

#define kalloc(size) \
  kheap_alloc(KHEAP_DEFAULT, size, Z_WAITOK)

kheap_alloc(src/Kernel/xnu/osfmk/kern/kalloc.h)

#define kheap_alloc(kalloc_heap, size, flags)                           \
  ({ VM_ALLOC_SITE_STATIC(0, 0);                                  \
  kalloc_ext(kalloc_heap, size, flags, &site).addr; })

zalloc_ext (src/Kernel/xnu/osfmk/kern/zalloc.c)

/*!
 * @function zalloc_ext
 *
 * @brief
 * The core implementation of @c zalloc(), @c zalloc_flags(), @c zalloc_percpu().
 */
/*
 * zalloc 族统一入口:给定 zone、统计视图 zstats、flags,返回指向一块 zone 元素的指针。
 * zone_t:分区描述;zone_stats:该 view/kheap 的 per-cpu 统计;zalloc_flags:Z_WAITOK/Z_NOWAIT/Z_NOFAIL 等。
 * 正文:断言「可阻塞/上下文」与 Z_NOFAIL 用法,必要时走 gzalloc,再在「pcpu magazine」与「zalloc_item」间二选一。
 */
void *
zalloc_ext(zone_t zone, zone_stats_t zstats, zalloc_flags_t flags)
{
  /*
   * KASan uses zalloc() for fakestack, which can be called anywhere.
   * However, we make sure these calls can never block.
   */
  assert(zone->kasan_fakestacks ||
      ml_get_interrupts_enabled() ||
      ml_is_quiescing() ||
      debug_mode_active() ||
      startup_phase < STARTUP_SUB_EARLY_BOOT);
  /*
   * Make sure Z_NOFAIL was not obviously misused
   */
  if (zone->z_replenishes) {
    assert((flags & (Z_NOWAIT | Z_NOPAGEWAIT)) == 0);
  } else if (flags & Z_NOFAIL) {
    assert(!zone->exhaustible &&
        (flags & (Z_NOWAIT | Z_NOPAGEWAIT)) == 0);
  }
#if CONFIG_GZALLOC
  if (__improbable(zone->gzalloc_tracked)) {
    return zalloc_gz(zone, zstats, flags);
  }
#endif /* CONFIG_GZALLOC */
  if (zone->z_pcpu_cache) {
    return zalloc_cached(zone, zstats, flags);
  }
  return zalloc_item(zone, zstats, flags);
}

zalloc_gz (src/Kernel/xnu/osfmk/kern/zalloc.c)

/*!
 * @function zalloc_gz
 *
 * @brief
 * Performs allocations for zones using gzalloc.
 *
 * @discussion
 * This function is noinline so that it doesn't affect the codegen
 * of the fastpath.
 */
__attribute__((noinline))
static void *
zalloc_gz(zone_t zone, zone_stats_t zstats, zalloc_flags_t flags)
{
  vm_offset_t addr = gzalloc_alloc(zone, zstats, flags);
  return zalloc_return(zone, zone_element_encode(addr, 0, ZPM_AUTO),
             flags, zone_elem_size(zone), NULL);
}

zalloc_return (src/Kernel/xnu/osfmk/kern/zalloc.c)

/*!
 * @function zalloc_return
 *
 * @brief
 * Performs the tail-end of the work required on allocations before the caller
 * uses them.
 *
 * @discussion
 * This function is called without any zone lock held,
 * and preemption back to the state it had when @c zalloc_ext() was called.
 *
 * @param zone          The zone we're allocating from.
 * @param ze            The encoded element we just allocated.
 * @param flags         The flags passed to @c zalloc_ext() (for Z_ZERO).
 * @param elem_size     The element size for this zone.
 * @param freemag       An optional magazine that needs to be freed.
 */
__attribute__((noinline))
static void *
/* freemag:若为从 magazine/depot 换新弹夹时换下的一叠空杂志,在此处释放:item_fast 通常为 NULL。 */
zalloc_return(zone_t zone, zone_element_t ze, zalloc_flags_t flags,
    vm_offset_t elem_size, zone_magazine_t freemag)
{
  /* 把编码后的元素 ze 转成「当前 elem_size」下的线性内核虚拟地址(供清零/tag 使用)。 */
  vm_offset_t addr = zone_element_addr(ze, elem_size);
#if KASAN_ZALLOC
  /* KASAN:shadow 标记整段对象为 ASAN_VALID,后续 load/store 才合法。 */
  if (zone->z_percpu) {
    /* per-cpu zone:每个 CPU 有独立偏移副本,对每个副本各标 VALID。 */
    zpercpu_foreach_cpu(i) {
      kasan_poison_range(addr + ptoa(i), elem_size,
          ASAN_VALID);
    }
  } else {
    /* 普通 zone:单块连续 VA 标记即可。 */
    kasan_poison_range(addr, elem_size, ASAN_VALID);
  }
#endif
#if ZALLOC_ENABLE_POISONING
  /* 可选运行时毒化/校验(与 CANARY 一类),检出元数据与用户大小不一致等问题。 */
  zalloc_validate_element(zone, addr, elem_size, zone_element_prot(ze));
#endif /* ZALLOC_ENABLE_POISONING */
#if ZONE_ENABLE_LOGGING || CONFIG_ZLEAKS
  /* 日志或泄漏检测:按 zone/大小概率采样,记下分配点栈。 */
  if (__improbable(zalloc_should_log_or_trace_leaks(zone, elem_size))) {
    zalloc_log_or_trace_leaks(zone, addr, __builtin_frame_address(0));
  }
#endif /* ZONE_ENABLE_LOGGING || CONFIG_ZLEAKS */
#if KASAN_ZALLOC
  /* Zone 配置了左右 redzone:缩小「用户可见」长度并在两侧留白给 KASAN。 */
  if (zone->z_kasan_redzone) {
    addr = kasan_alloc(addr, elem_size,
        elem_size - 2 * zone->z_kasan_redzone,
        zone->z_kasan_redzone);
    elem_size -= 2 * zone->z_kasan_redzone;
  }
  /*
   * Initialize buffer with unique pattern only if memory
   * wasn't expected to be zeroed.
   */
  /* 空闲回收未保证零且未要求 Z_ZERO 时写入 KASAN 泄漏追踪图案(便于认出未初始化使用)。 */
  if (!zone->z_free_zeroes && !(flags & Z_ZERO)) {
    kasan_leak_init(addr, elem_size);
  }
#endif /* KASAN_ZALLOC */
  /* 调用方要带 Z_ZERO 而该 zone free 时不自动清零 → 在此处整块 bzero。 */
  if ((flags & Z_ZERO) && !zone->z_free_zeroes) {
    bzero((void *)addr, elem_size);
  }
#if VM_MAX_TAG_ZONES
  /* 打开 tag 统计的 zone:把 vm_tag 写入元素旁 slot,更新按 tag 的计数。 */
  if (__improbable(zone->tags)) {
    /* flags 里的 tag;未指定则用通用 kalloc 类。 */
    vm_tag_t tag = zalloc_flags_get_tag(flags);
    if (tag == VM_KERN_MEMORY_NONE) {
      tag = VM_KERN_MEMORY_KALLOC;
    }
    // set the tag with b0 clear so the block remains inuse
    /* 存入 (*2) 且保持 bit0 为 0:与「释放时 bit0 置位」一类约定兼容,表示仍为 in-use。 */
    *ztSlot(zone, addr) = (vm_tag_t)(tag << 1);
    vm_tag_update_zone_size(tag, zone->tag_zone_index,
        (long)elem_size);
  }
#endif /* VM_MAX_TAG_ZONES */
  /* Mach 泄漏探测器与 DTrace 探针(大小与指针)。 */
  TRACE_MACHLEAKS(ZALLOC_CODE, ZALLOC_CODE_2, elem_size, addr);
  DTRACE_VM2(zalloc, zone_t, zone, void*, addr);
  /* 若在 cached 路径上替换了 magazine,这里归还换下的空 magazine。 */
  if (freemag) {
    zone_magazine_free(freemag);
  }
  /* 契约:无 zone 锁、抢占状态与进入 zalloc_ext 时一致;返回用户缓冲区指针。 */
  return (void *)addr;
}

zone_element_encode (src/Kernel/xnu/osfmk/kern/zalloc.c)

static zone_element_t
zone_element_encode(vm_offset_t base, vm_offset_t eidx, zprot_mode_t zpm)
{
  return (zone_element_t){ .ze_value = base | (eidx << 2) | zpm };
}

zone_element_addr (src/Kernel/xnu/osfmk/kern/zalloc.c)

static vm_offset_t
zone_element_addr(zone_element_t ze, vm_offset_t esize)
{
  return zone_element_base(ze) + esize * zone_element_idx(ze);
}

崩溃原因

zalloc_gz 里写的是:

zone_element_encode(raw_addr, 0, ZPM_AUTO)

ze = zone_element_encode(raw_addr, 0, ZPM_AUTO) ≡ raw_addr (ZPM_AUTO == 0)

idx = (ze_value & PAGE_MASK) >> 2; 

vm_offset_t addr = zone_element_addr(ze, elem_size); = trunc_page(ze) + esize * idx; = ze + esize * idx != ze

返回的虚拟地址 addr 比 已经映射物理地址的 raw_addr 高了 esize * idx ,导致返回的虚拟地址 addr 没有 映射物理地址

3.apple 原生 BootKernelExtensions.kc 存在这个问题吗?

zalloc_ext (src/Kernel/xnu/osfmk/kern/zalloc.c)

/*!
 * @function zalloc_ext
 *
 * @brief
 * The core implementation of @c zalloc(), @c zalloc_flags(), @c zalloc_percpu().
 */
/*
 * zalloc 族统一入口:给定 zone、统计视图 zstats、flags,返回指向一块 zone 元素的指针。
 * zone_t:分区描述;zone_stats:该 view/kheap 的 per-cpu 统计;zalloc_flags:Z_WAITOK/Z_NOWAIT/Z_NOFAIL 等。
 * 正文:断言「可阻塞/上下文」与 Z_NOFAIL 用法,必要时走 gzalloc,再在「pcpu magazine」与「zalloc_item」间二选一。
 */
void *
zalloc_ext(zone_t zone, zone_stats_t zstats, zalloc_flags_t flags)
{
  ...
#if CONFIG_GZALLOC
  if (__improbable(zone->gzalloc_tracked)) {
    return zalloc_gz(zone, zstats, flags);
  }
#endif /* CONFIG_GZALLOC */
   ...
}

1.lldb调试 apple 原生 BootKernelExtensions.kc 观察 有没有进入 return zalloc_gz(zone, zstats, flags);

apple 原生 BootKernelExtensions.kc 对应汇编代码

; === zalloc_ext 入口 ===
FFFFFF80002EC1B0  push    rbp
FFFFFF80002EC1B1  mov     rbp, rsp
; zalloc.c:6246  if (zone->gzalloc_tracked)  ← 你要验证的分支
FFFFFF80002EC1B4  test    byte ptr [rdi+3Eh], 2    ; gzalloc_tracked (bit1)
FFFFFF80002EC1B8  jnz     short loc_FFFFFF80002EC216 ; taken → gzalloc 路径

进行lldb调试,发现 apple 原生 BootKernelExtensions.kc 并没有调用 return zalloc_gz(zone, zstats, flags);

2.apple 原生 BootKernelExtensions.kc 为什么没有调用 return zalloc_gz(zone, zstats, flags); ?

gzalloc_configure (src/Kernel/xnu/osfmk/kern/gzalloc.c)

__startup_func
static void
gzalloc_configure(void)
{
#if !KASAN_ZALLOC
  char temp_buf[16];
  if (PE_parse_boot_argn("-gzalloc_mode", temp_buf, sizeof(temp_buf))) {
    gzalloc_mode = TRUE;
    gzalloc_min = GZALLOC_MIN_DEFAULT;
    gzalloc_max = ~0U;
  }
  if (PE_parse_boot_argn("gzalloc_min", &gzalloc_min, sizeof(gzalloc_min))) {
    gzalloc_mode = TRUE;
    gzalloc_max = ~0U;
  }
  if (PE_parse_boot_argn("gzalloc_max", &gzalloc_max, sizeof(gzalloc_max))) {
    gzalloc_mode = TRUE;
    if (gzalloc_min == ~0U) {
      gzalloc_min = 0;
    }
  }
  if (PE_parse_boot_argn("gzalloc_size", &gzalloc_size, sizeof(gzalloc_size))) {
    gzalloc_min = gzalloc_max = gzalloc_size;
    gzalloc_mode = TRUE;
  }
  (void)PE_parse_boot_argn("gzalloc_fc_size", &gzfc_size, sizeof(gzfc_size));
  if (PE_parse_boot_argn("-gzalloc_wp", temp_buf, sizeof(temp_buf))) {
    gzalloc_prot = VM_PROT_READ;
  }
  if (PE_parse_boot_argn("-gzalloc_uf_mode", temp_buf, sizeof(temp_buf))) {
    gzalloc_uf_mode = TRUE;
    gzalloc_guard = KMA_GUARD_FIRST;
  }
  if (PE_parse_boot_argn("-gzalloc_no_dfree_check", temp_buf, sizeof(temp_buf))) {
    gzalloc_dfree_check = FALSE;
  }
  (void) PE_parse_boot_argn("gzalloc_zscale", &gzalloc_zonemap_scale, sizeof(gzalloc_zonemap_scale));
  if (PE_parse_boot_argn("-gzalloc_noconsistency", temp_buf, sizeof(temp_buf))) {
    gzalloc_consistency_checks = FALSE;
  }
  if (PE_parse_boot_argn("gzname", gznamedzone, sizeof(gznamedzone))) {
    gzalloc_mode = TRUE;
  }
#if DEBUG
  if (gzalloc_mode == FALSE) {
    gzalloc_min = 1024;
    gzalloc_max = 1024;
    strlcpy(gznamedzone, "pmap", sizeof(gznamedzone));
    gzalloc_prot = VM_PROT_READ;
    gzalloc_mode = TRUE;
  }
#endif
  if (PE_parse_boot_argn("-nogzalloc_mode", temp_buf, sizeof(temp_buf))) {
    gzalloc_mode = FALSE;
  }
  if (gzalloc_mode) {
    gzalloc_reserve_size = GZALLOC_RESERVE_SIZE_DEFAULT;
    gzalloc_reserve = (vm_offset_t) pmap_steal_memory(gzalloc_reserve_size);
  }
#endif
}

差异点是 

#if DEBUG
  if (gzalloc_mode == FALSE) {
    gzalloc_min = 1024;
    gzalloc_max = 1024;
    strlcpy(gznamedzone, "pmap", sizeof(gznamedzone));
    gzalloc_prot = VM_PROT_READ;
    gzalloc_mode = TRUE;
  }
#endif

RELEASE(Apple 零售/RELEASE KC):没有上面 #if DEBUG 块 → 除非 boot-arg 显式开启,否则 gzalloc_mode 一直为 FALSE → gzalloc_zone_init() 直接 return,所有 zone 的 gzalloc_tracked 保持 0 → zalloc_ext 永远不会进 zalloc_gz。

所有 让 gzalloc_mode = 0 ,即可和 apple 原生 BootKernelExtensions.kc kalloc 调用路径一致

2.自己编译的内核如何关闭 gzalloc_mode ?

在编译的引导文件 OpenCore-master.iso config.plist boot-arg 新增 -nogzalloc_mode即可,boot-arg="keepsyms=1 -v debug=0x108 zone_array_dump=1 gzalloc_alloc_debug=1 -nogzalloc_mode"

3.自己编译的内核关闭 gzalloc_mode 后效果

可以看到虽然还是有崩溃,但是已经 不是之前崩溃了,而是后面新的崩溃,已经解决 kalloc 返回的 虚拟地址没有映射物理地址的问题

(lldb) c
Process 1 resuming
Process 1 stopped
* thread #1, stop reason = breakpoint 7.1 8.1
    frame #0: 0xffffff8010a9d7f4 BootKernelExtensions_o.kc`panic(str="Kernel trap at 0x%016llx, type %d=%s, registers:\nCR0: 0x%016llx, CR2: 0x%016llx, CR3: 0x%016llx, CR4: 0x%016llx\nRAX: 0x%016llx, RBX: 0x%016llx, RCX: 0x%016llx, RDX: 0x%016llx\nRSP: 0x%016llx, RBP: 0x%016llx, RSI: 0x%016llx, RDI: 0x%016llx\nR8:  0x%016llx, R9:  0x%016llx, R10: 0x%016llx, R11: 0x%016llx\nR12: 0x%016llx, R13: 0x%016llx, R14: 0x%016llx, R15: 0x%016llx\nRFL: 0x%016llx, RIP: 0x%016llx, CS:  0x%016llx, SS:  0x%016llx\nFault CR2: 0x%016llx, Error code: 0x%016llx, Fault CPU: 0x%x%s%s%s%s, PL: %d, VF: %d\n") at debug.c:802:2 [opt]
BootKernelExtensions_o.kc`panic:
->  0xffffff8010a9d7f4 <+46>: movq   %rcx, (%rsi)
    0xffffff8010a9d7f7 <+49>: leaq   0x10(%rbp), %rcx
    0xffffff8010a9d7fb <+53>: movq   %rcx, 0x8(%rsi)
    0xffffff8010a9d7ff <+57>: movq   %rax, 0x10(%rsi)
Target 1: (boot.efi) stopped.
(lldb) bt
* thread #1, stop reason = breakpoint 7.1 8.1
  * frame #0: 0xffffff8010a9d7f4 BootKernelExtensions_o.kc`panic(str="Kernel trap at 0x%016llx, type %d=%s, registers:\nCR0: 0x%016llx, CR2: 0x%016llx, CR3: 0x%016llx, CR4: 0x%016llx\nRAX: 0x%016llx, RBX: 0x%016llx, RCX: 0x%016llx, RDX: 0x%016llx\nRSP: 0x%016llx, RBP: 0x%016llx, RSI: 0x%016llx, RDI: 0x%016llx\nR8:  0x%016llx, R9:  0x%016llx, R10: 0x%016llx, R11: 0x%016llx\nR12: 0x%016llx, R13: 0x%016llx, R14: 0x%016llx, R15: 0x%016llx\nRFL: 0x%016llx, RIP: 0x%016llx, CS:  0x%016llx, SS:  0x%016llx\nFault CR2: 0x%016llx, Error code: 0x%016llx, Fault CPU: 0x%x%s%s%s%s, PL: %d, VF: %d\n") at debug.c:802:2 [opt]
    frame #1: 0xffffff80103c58f6 BootKernelExtensions_o.kc`panic_trap(regs=0xffffffa028ac3660, pl=<unavailable>, fault_result=<unavailable>) at trap.c:841:2 [opt]
    frame #2: 0xffffff80103c55dd BootKernelExtensions_o.kc`kernel_trap(state=<unavailable>, lo_spp=<unavailable>) at trap.c:780:2 [opt]
    frame #3: 0xffffff8010232a2f BootKernelExtensions_o.kc`trap_from_kernel + 38
    frame #4: 0xffffff8013590d0e
    frame #5: 0xffffff8013562a5d
    frame #6: 0xffffff801355c2ed
    frame #7: 0xffffff801354df61
    frame #8: 0xffffff8013547126
    frame #9: 0xffffff80135a1f96
    frame #10: 0xffffff801095cccc BootKernelExtensions_o.kc`OSKext::start(this=<unavailable>, startDependenciesFlag=<unavailable>) at OSKext.cpp:7462:12 [opt]
    frame #11: 0xffffff801095a22c BootKernelExtensions_o.kc`OSKext::load(this=0xffffff934f0743a0, startOpt='\0', startMatchingOpt='\x02', personalityNames=0x0000000000000000) at OSKext.cpp:5845:13 [opt]
    frame #12: 0xffffff801096f12d BootKernelExtensions_o.kc`OSKext::loadKextWithIdentifier(kextIdentifier=0xffffff86828b5620, kextRef=0x0000000000000000, allowDeferFlag=false, delayAutounloadFlag=false, startOpt='\0', startMatchingOpt='\x02', personalityNames=0x0000000000000000) at OSKext.cpp:5232:20 [opt]
    frame #13: 0xffffff801096ef43 BootKernelExtensions_o.kc`OSKext::loadKextWithIdentifier(kextIdentifierCString="com.apple.kec.corecrypto", allowDeferFlag=false, delayAutounloadFlag=false, startOpt='\0', startMatchingOpt='\x02', personalityNames=0x0000000000000000) at OSKext.cpp:5100:11 [opt]
    frame #14: 0xffffff8010c1135e
    frame #15: 0xffffff8010c10ba0
    frame #16: 0xffffff80109b17e1 BootKernelExtensions_o.kc`InitIOKit(dtTop=<unavailable>) at IOStartIOKit.cpp:196:3 [opt]
    frame #17: 0xffffff8010a74864 BootKernelExtensions_o.kc`PE_init_iokit at pe_init.c:191:2 [opt]
    frame #18: 0xffffff80102bc46f BootKernelExtensions_o.kc`kernel_bootstrap_thread at startup.c:661:2 [opt]
    frame #19: 0xffffff801023213e BootKernelExtensions_o.kc`call_continuation + 46
(lldb) c
Process 1 resuming
(lldb)

4.总结

本次 kalloc 返回虚拟地址没有映射 物理地址的问题,又是DEBUG 和 RELEASE 编译代码不同代码导致的,通过boot-arg调整 自己编译的 DEBUG xnu 内核 调整和 apple 原生 BootKernelExtensions.kc 一样的 代码逻辑后,解决了这个问题


5. 遗留问题

我自己编译的 xnu 内核 即使在DEBUG模式下,逻辑应该是自洽的,我没有修改任何内核代码,不应该会出现 

返回的虚拟地址 addr 比 已经映射物理地址的 raw_addr 高了  esize * idx ,导致返回的虚拟地址 addr 没有 映射物理地址

这种情况,需要进一步排查






传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 11小时前 被内核蛆编辑 ,原因:
收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回