32-bit efi and 64-bit kernel

Just want to make it clear.

32-bit efi CAN run 64-bit kernel if Apple only would want it.

Just look at hackint0sh world, chameleon is 32-bit bootloader and runs 64-bit kernel.

If hackers can, do you really think apple cant ?

now some technical details to back this up.

What bootloader does (boot.efi or chameleon) is
1) load kernel and mkext(kext cache) to memory
2) construct boot arguments, its just structs, arch-independent
3) pass pointer to boot arguments to kernel via eax register
4) jump to kernel entry point (_start function)

nothing really hard and architecture dependent.

now lets look at osfmk/i386/start.s from xnu sources

* BSP CPU start here.
* eax points to kernbootstruct
* Environment:
* protected mode, no paging, flat 32-bit address space.
* (Code/data/stack segments have base == 0, limit == 4G)
call EXT(vstart) /* run C code */

SURPRISE! no matter what efi you have, kernel starts in 32-bit mode.

at the end it calls vstart() function from osfmk/i386/i386_init.c

lets take a quick look to it

vstart(vm_offset_t boot_args_start)
#ifdef __x86_64__

bold part is what running in x86_64 compiled kernel, and surprise, x86_64 kernel switches itself to x86_64, without any help of bootloader.

So, to sum it all, 64-bit kernel run in 32-bit mode then switches itself to 64-bit mode, it doesn’t care about what efi you have, 64-bit or 32-bit.

One last thing. Kernel calls efi runtime services to work with things like nvram variables. One may wonder, will 32-bit kernel will be able to call 64-bit efi functions.

Lets see, osfmk/i386/AT386/model_dep.c:

static void
efi_set_tables_64(EFI_SYSTEM_TABLE_64 * system_table)

if (!cpu_mode_is64bit()) {
kprintf("Skipping 64-bit EFI runtime services for 32-bit legacy mode\n");
kprintf("RuntimeServices table at 0x%qx\n", system_table->RuntimeServices);
// 64-bit virtual address is OK for 64-bit EFI and 64/32-bit kernel.

so, with 64-bit efi and 32-bit kernel they just skip runtime services, but it’s not our case, next is

static void
efi_set_tables_32(EFI_SYSTEM_TABLE_32 * system_table)
// 32-bit virtual address is OK for 32-bit EFI and 32-bit kernel.
// For a 64-bit kernel, booter will ensure pointer is zeroed out

so, no runtime services for 32-bit kernel and 64-bit efi, but it’s same as previous case.

now look at bold string, it means they was already running 64-bit kernel on top of 32-bit efi.

What it all means – you will get 64-bit kernel on 32-bit efi macs when Apple will want it, if they will. There is nothing technical that stops it.


  1. June 22nd, 2011 | 6:26 am

    frosty piss!

  2. LoLL
    June 22nd, 2011 | 10:56 am

    Thanks for clarification.

    It’s quite impressive that i386/X86_64 CPUs can run same binary regardless the memory model used… clap clap

  3. Someone
    June 22nd, 2011 | 5:24 pm

    Interesting stuff. Unfortunately, this would apply only to original Mac Pro owners and folks who upgraded their Core Duos, correct? Apple has never really gone out of their way to support modders and they already left the first-gen MP out in the cold in booting the 64-bit kernel. Wish it were different, but Apple loves chopping off the low end.

  4. Azimutz
    June 22nd, 2011 | 8:44 pm

    I’m pretty sure we’ll revert this stuff as soon as kernel sources get out, if feasible… won’t we Nawcom!? 😛 yeah, i didn’t forgot this! I’ll get to you soon about the legacy_kernel.
    My self is very interested in it, since i don’t pretend to move from the Pentium D so soon.

    Some of this stuff i already knew from playing with fake_efi.c a while ago; never cross checked it with the kernel, though…
    Thanks for the insight, Netkas.

  5. June 22nd, 2011 | 9:36 pm

    Lemon Frosty

  6. September 2nd, 2011 | 8:13 am

    you can write a small thunk between 64bit kernel and 32bit EFI and report an EFI64 runtime services to kernel.

  7. rbf
    February 5th, 2012 | 3:13 am

    Interesting! I poked around and found this in i386_init() of i386_init.c. It seems to be the only place they are forcing kernels into legacy mode. Maybe worth it to try building a modified kernel and testing it out on a Mac Pro 1,1. I’d love to get my Mac out of “legacy mode”. I didn’t know that Apple open sourced OSX again until today.

    * At this point we check whether we are a 64-bit processor
    * and that we’re not restricted to legacy mode, 32-bit operation.
    if (cpuid_extfeatures() & CPUID_EXTFEATURE_EM64T) {
    boolean_t legacy_mode;
    kprintf(“EM64T supported”);
    if (PE_parse_boot_argn(“-legacy”, &legacy_mode, sizeof (legacy_mode))) {
    kprintf(” but legacy mode forced\n”);
    IA32e = FALSE;
    } else {
    kprintf(” and will be enabled\n”);
    } else
    IA32e = FALSE;

  8. rbf
    February 5th, 2012 | 3:27 am

    After the above check they pass the IA32e(Intel’s old 64 bit moniker) flag into i386_vm_init() and then finish bootstrapping. I haven’t looked much deeper but maybe the rest of the system keys off of what vm model is setup for 32/64 initialization. You’d figure Apple would make the check easy to get rid of since they like to phase old hardware and code out fast. It would be nice if it were as simple as an undefine of CONFIG_YONAH lol.

    if (!(cpuid_extfeatures() & CPUID_EXTFEATURE_XD))
    nx_enabled = 0;

    * VM initialization, after this we’re using page tables…
    * The maximum number of cpus must be set beforehand.
    i386_vm_init(maxmemtouse, IA32e, kernelBootArgs);

    /* create the console for verbose or pretty mode */
    /* Note: doing this prior to tsc_init() allows for graceful panic! */
    PE_init_platform(TRUE, kernelBootArgs);




  9. rbf
    February 5th, 2012 | 5:28 am

    Traced through i386_vm_init() down into pmap_bootstrap() and it seems like the value IA32e is driving the init to either 23 bit or 64 bit. But will it work…

    /* 32-bit and legacy support depends on IA32e mode being disabled */
    cpu_64bit = IA32e;

    if (cpu_64bit) {
    pdpt_entry_t *ppdpt = IdlePDPT;
    pdpt_entry_t *ppdpt64 = (pdpt_entry_t *)IdlePDPT64;
    pdpt_entry_t *ppml4 = (pdpt_entry_t *)IdlePML4;
    int istate = ml_set_interrupts_enabled(FALSE);

    * Clone a new 64-bit 3rd-level page table directory, IdlePML4,
    * with page bits set for the correct IA-32e operation and so that
    * the legacy-mode IdlePDPT is retained for slave processor start-up.
    * This is necessary due to the incompatible use of page bits between
    * 64-bit and legacy modes.
    kernel_pmap->pm_cr3 = (pmap_paddr_t)((int)IdlePML4); /* setup in start.s for us */
    kernel_pmap->pm_pml4 = IdlePML4;
    kernel_pmap->pm_pdpt = (pd_entry_t *)
    ((unsigned int)IdlePDPT64 | KERNBASE );
    (uint32_t)IdlePDPT64 | PAGE_BITS);
    pmap_store_pte((ppdpt64+0), *(ppdpt+0) | PAGE_BITS);
    pmap_store_pte((ppdpt64+1), *(ppdpt+1) | PAGE_BITS);
    pmap_store_pte((ppdpt64+2), *(ppdpt+2) | PAGE_BITS);
    pmap_store_pte((ppdpt64+3), *(ppdpt+3) | PAGE_BITS);

    * The kernel is also mapped in the uber-sapce at the 4GB starting
    * 0xFFFFFF80:00000000. This is the highest entry in the 4th-level.
    pmap_store_pte((ppml4+KERNEL_UBER_PML4_INDEX), *(ppml4+0));

    kernel64_cr3 = (addr64_t) kernel_pmap->pm_cr3;

    /* Re-initialize descriptors and prepare to switch modes */
    current_cpu_datap()->cpu_is64bit = TRUE;
    current_cpu_datap()->cpu_active_cr3 = kernel64_cr3;

    pde_mapped_size = 512*4096 ;


    /* Sets 64-bit mode if required. */
    /* Update in-kernel CPUID information if we’re now in 64-bit mode */
    if (IA32e)

    kernel_pmap->pm_hold = (vm_offset_t)kernel_pmap->pm_pml4;

    kprintf(“Kernel virtual space from 0x%x to 0x%x.\n”,
    VADDR(KPTDI,0), virtual_end);
    printf(“PAE enabled\n”);
    if (cpu_64bit){
    printf(“64 bit mode enabled\n”);kprintf(“64 bit mode enabled\n”); }

  10. March 28th, 2012 | 6:26 pm

    rbf, so how do you think? Is it real to make changes in kernel for booting to x86_64 mode??

Leave a reply