Skip to content

lldb stepping fails in macOS processes with a 32-bit/custom LDT installed #57591

Closed
@mrpippy

Description

@mrpippy

macOS 10.15 added support for processes to set a custom LDT, allowing a 64-bit process to set up and far jump to a 32-bit code segment. Wine (specifically CrossOver by CodeWeavers) uses this functionality to run 32-bit Windows EXEs.

However, lldb is not able to correctly step when in the non-64-bit code segment. Stepping resets %cs back to the 64-bit segment, generally causing a crash soon after (because 32-bit instructions are being executed as 64-bit).

I've attached a sample executable derived from an XNU test which tests the custom LDT functionality. It's pretty easy to reproduce the bug. Also note that although Rosetta 2 implements the custom LDT functionality, I wasn't able to reproduce this bug there. Run this on an Intel Mac:

lldb-32bit-ldt.zip

% sw_vers
ProductName:	macOS
ProductVersion:	12.5
BuildVersion:	21G72
% uname -a
Darwin Brendans-Mac-mini.local 21.6.0 Darwin Kernel Version 21.6.0: Sat Jun 18 17:07:25 PDT 2022; root:xnu-8020.140.41~1/RELEASE_X86_64 x86_64

% cd lldb-32bit-ldt
% make
cc -arch x86_64 -g3 -o ldt ldt.c ldt_code32.s -Wall -Wextra -Wl,-pagezero_size,0x1000 -DDEBUG

% lldb ./ldt
(lldb) target create "./ldt"
Current executable set to '/Users/pip/lldb-32bit-ldt/ldt' (x86_64).
(lldb) version
lldb-1316.0.9.46
Apple Swift version 5.6.1 (swiftlang-5.6.0.323.66 clang-1316.0.20.12)
(lldb) break set -n code_32
Breakpoint 1: where = ldt`code_32, address = 0x000000000000b000
(lldb) run
Process 82565 launched: '/Users/pip/lldb-32bit-ldt/ldt' (x86_64)
32-bit code is at 0xb000
lowstack addr = 0x210ff0
size = 8192, szlimit = 32768
Mapping code @0xa000..0xb04b => 0xf0000..0xf104b
i386_get_ldt returned 3
i386_set_ldt returned 3
base 0x0 lim 0x0 type 0x0 dpl 0 present 0 opsz 0 granular 0
base 0x0 lim 0xfffff type 0x12 dpl 3 present 1 opsz 1 granular 1
base 0x0 lim 0x0 type 0x0 dpl 0 present 0 opsz 0 granular 0
base 0x0 lim 0xfffff type 0x1a dpl 3 present 1 opsz 1 granular 1
Updated gsbase for stack at 0x201000..0x211000 to 0x7000033880e0
[thread 0x700003388000] tsd base => 0x7000033880e0
Setting new GS base: 0x1008200
Process 82565 stopped
* thread #2, stop reason = EXC_BREAKPOINT (code=EXC_I386_BPT, subcode=0x0)
    frame #0: 0x00000000000f1001
->  0xf1001: movl   %esp, %ebp
    0xf1003: pushq  %rbx
    0xf1004: callq  0xf1009
    0xf1009: popq   %rbx
Target 0: (ldt) stopped.
(lldb) reg read $cs
      cs = 0x000000000000001f                           < ------ 32-bit code segment
(lldb) stepi
Process 82565 stopped
* thread #2, stop reason = instruction step into
    frame #0: 0x00000000000f1003
->  0xf1003: pushq  %rbx
    0xf1004: callq  0xf1009
    0xf1009: popq   %rbx
    0xf100a: subl   $0x8, %esp
Target 0: (ldt) stopped.
(lldb) reg read $cs
      cs = 0x000000000000002b                            < ------ after one step, %cs is now the 64-bit code segment
(lldb) reg read $rsp
     rsp = 0x0000000000210f98
(lldb) stepi
Process 82565 stopped
* thread #2, stop reason = instruction step into
    frame #0: 0x00000000000f1004
->  0xf1004: callq  0xf1009
    0xf1009: popq   %rbx
    0xf100a: subl   $0x8, %esp
    0xf100d: movl   0x1c(%rbp), %eax
Target 0: (ldt) stopped.
(lldb) reg read $rsp
     rsp = 0x0000000000210f90                            < ------ the processor really is in 64-bit mode now, $rsp decreased by 0x8

I'm not sure, but this bug might be related to macOS adding a 'full' thread state flavor (x86_THREAD_FULL_STATE64). I believe it's only (and I think must be) used for processes that have set a custom LDT. This flavor contains the normal x86_64 thread state, but also ds, es, ss, and GSbase. Maybe lldb needs to use the full thread state flavor when it's available to avoid %gs being reset?

_STRUCT_X86_THREAD_FULL_STATE64
{
        _STRUCT_X86_THREAD_STATE64      __ss64;
        __uint64_t                      __ds;
        __uint64_t                      __es;
        __uint64_t                      __ss;
        __uint64_t                      __gsbase;
};

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions