Skip to content

rust-lldb: Incorrectly display Option::None as Option::Some #152743

Description

@zzjrabbit

I am developing a kernel, and when I tried debugging with rust-lldb, I found this:

pm1b = Some({gas:{address_space:SystemMemory, bit_width:'\x99', bit_offset:'\0', access_size:'\0', address:234201088}, handler:(), mapping:Some({physical_start:18446603338569718192, virtual_start:{pointer:"APIC\x99"}, region_length:18446603338569718160, mapped_length:18446603336455397376, handler:()})}) {
    0 = {
      gas = {
        address_space = SystemMemory
        bit_width = '\x99'
        bit_offset = '\0'
        access_size = '\0'
        address = 234201088
      }
      handler =
      mapping = Some({physical_start:18446603338569718192, virtual_start:{pointer:"APIC\x99"}, region_length:18446603338569718160, mapped_length:18446603336455397376, handler:()}) {
        0 = {
          physical_start = 18446603338569718192
          virtual_start = {
            pointer = 0xffff80000df5a000 "APIC\x99"
          }
          region_length = 18446603338569718160
          mapped_length = 18446603336455397376
          handler =
        }
      }
    }
  }

While I unwrapped pm1b with the following code:

let pm1 = &registers.pm1_control_registers;
log::debug!("pm1: {:?}", pm1.pm1b.is_some());
loop {
    let pm1b = pm1.pm1b.as_ref();
    pm1b.unwrap().write((slp_typa | (1 << 13)) as u64).unwrap();
}

And there's the output.

[DEBUG] pm1: false, kernel_hal/src/platform/bare/acpi/power.rs:20
[ERROR] Panic: panicked at kernel_hal/src/platform/bare/acpi/power.rs:23:14:
called `Option::unwrap()` on a `None` value

What made me sure that this is a bug of rust-lldb is the length of the region, which is too large to be valid.
And I am using 1.95.0-nightly (ce69df6f7 2026-02-12) toolchain, while the version of lldb is 21.1.8 from nixpkgs.

Activity

  1. added
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Feb 17, 2026
  2. eggyal commented on Feb 17, 2026

    @eggyal
    Contributor

    Smells like UB. Are you able to provide (hopefully minimized) example code that reproduces this error?

    @rustbot label +A-debuggers-lldb +S-needs-repro

  3. added
    S-needs-reproStatus: This issue has no reproduction and needs a reproduction to make progress.
    on Feb 17, 2026
  4. zzjrabbit commented on Feb 17, 2026

    @zzjrabbit
    Author

    Sorry, I can't. My experiments all failed. But I managed to make lldb raise out a lot of errors. The basic idea is to write something like 0xfe to the flag byte of Option, and write meaningless bytes into the following regions. However, I can't see why such things can happen, due to the fact that the data in the origin issue is stored in the heap, and there's no unsafe operation that could have messed it up.

  5. eggyal commented on Feb 17, 2026

    @eggyal
    Contributor

    write something like 0xfe to the flag byte of Option

    But that causes undefined behaviour.

  6. zzjrabbit commented on Feb 17, 2026

    @zzjrabbit
    Author

    I just realized that it was in the heap, but after representing the number rust-lldb printed in hex, I found that it's not that meaningless, because it has something to do with the address the kernel was loaded. However, I still can't see why rust-lldb thought it was Option::Some.

  7. kpreid commented on Feb 17, 2026

    @kpreid
    Contributor

    If the enum layout was “niche optimized”, then it is likely (but not guaranteed) that any value other than whatever was assigned to None would be treated as Some, at least as far as the compiled Rust code is concerned (I don't know how rust-lldb works). So, either rust-lldb is printing as accurately, or rust-lldb is printing invalid data as if it were valid data, but we don’t know without seeing the full definition of the type stored in the Option. Can you share it?

  8. zzjrabbit commented on Feb 18, 2026

    @zzjrabbit
    Author
  9. workingjubilee commented on Feb 18, 2026

    @workingjubilee
    Member

    @zzjrabbit

    Sorry, I can't. My experiments all failed. But I managed to make lldb raise out a lot of errors. The basic idea is to write something like 0xfe to the flag byte of Option, and write meaningless bytes into the following regions. However, I can't see why such things can happen, due to the fact that the data in the origin issue is stored in the heap, and there's no unsafe operation that could have messed it up.

    There is no guarantee that Option uses a "flag byte". The GenericAddress type that MappedGas contains this enum which would permit a niche optimization for any Option<MappedGas>: https://docs.rs/acpi/latest/acpi/address/enum.AddressSpace.html

    Do not assume the layout of Option where it is not in the very limited set of guaranteed cases, as it may adopt radically different layouts based on T.

  10. added
    C-gubCategory: the reverse of a compiler bug is generally UB
    and removed
    C-bugCategory: This is a bug.
    on Feb 18, 2026
  11. removed
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Feb 18, 2026
  12. removed
    C-gubCategory: the reverse of a compiler bug is generally UB
    on Feb 21, 2026
  13. workingjubilee commented on Feb 21, 2026

    @workingjubilee
    Member

    Hm, something's off here still, actually, that was a red herring now that I think about it. My apologies.

    @zzjrabbit Now that I look properly, the PR which added enum support to rust-lldb unfortunately made several tests not run in CI: #124781

    See #151966 for what I mean about "tests not running in CI".

    I believe this bug may be a duplicate of #150392 though.

  14. zzjrabbit commented on Feb 22, 2026

    @zzjrabbit
    Author

    It's okay, after all, you have to deal with so many issues. But I don't think this is a duplicate of #150392. Because in that issue's example, the compiler can easily optimize the layout of Option. However, in my case, this cannot happen, because there is no way to know whether the pm1 register block is present or not during compile time. So what's happening here might not be quite the same as what happened in that issue.

  15. Walnut356 commented on Mar 1, 2026

    @Walnut356
    Contributor

    I strongly suspect this is due to the implementation of the provider. Specifically, when it searches for a variant of the index that matches the discriminant. If it cant find a match, it defaults to 0th variant.

    If my hunch is right, the untagged variant isnt guaranteed to be the 0th variant, thus in some cases it can end up determining the wrong variant. For a more correct implementation, see this code.

    I'll look into this more as soon as i'm able, but if possible would you be able to modify getCurrentVariantIndex in your lldb_providers.py and see if the error still occurs?

  16. zzjrabbit commented on Mar 1, 2026

    @zzjrabbit
    Author

    Sorry, I can't, because a new term is about to start. And I think I have no time to do this in a few months.

  17. Walnut356 commented on Mar 1, 2026

    @Walnut356
    Contributor

    Oh, I'm an idiot. I've done this same investigation like 3 times now and I keep forgetting it's still broken. The current visualizer script is written in a way that looks wrong at first, but it'll technically do the correct thing.

    The underlying issue is LLDB's fault. I opened an issue for it a month ago.

    TL;DR, when LLDB reads variant member DWARF info, they read it into a uint32_t. When they create the variant names, they directly append this discriminant value to the variant name. Discriminant values that rustc outputs can be up to 128 bits.

    String wraps Vec, and Vec is effectively a {ptr: NonNull<u8> , cap: NoHighBit<usize> , len: usize}. For whatever reason rustc niche-optimizes off of the NoHighBit rather than the NonNull. This means the null-variant's value is 1<<63. C++ silently truncates this value to 0 to fit it in the uint32_t. When the discr is read at debug-time, it's read as an unsigned long long, since that's the type the DWARF data reports it as. It accurately reads 9223372036854775808. The visualizer scripts compare that against 0 and don't find a match, so it defaults to the untagged variant (which is Some).

    Since None's expected variant is 0, if we have an Option<String> with capacity 0, the discr at run-time is 0. This causes a match on the None variant, which is why empty strings are also visualized incorrectly.

    Since LLDB is just straight up reading the number wrong, there's not much I can do to modify the visualizer scripts or rustc output to fix this without causing even bigger issues (e.g. discr = discr & u32::MAX before comparison)

    This also explains why it's an intermittent issue. Technically, it affects all sum-type enums on *-gnu targets, including non-niche enums, but it's very often not a problem because usually discr < u32::MAX, or defaulting to the untagged variant is coincidentally correct.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-debuggers-lldbArea: lldbC-bugCategory: This is a bug.S-needs-reproStatus: This issue has no reproduction and needs a reproduction to make progress.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions