[llvm] [llvm-readobj] Display Mach-O CPU capability bits (PR #221617)

Tianyi Chen via llvm-commits llvm-commits at lists.llvm.org
Sun Sep 6 17:34:03 PDT 2026


https://github.com/chenty2333 created https://github.com/llvm/llvm-project/pull/221617

Mach-O file header output currently masks the capability bits out of
`cpusubtype` without printing them separately. For example, an ARM64e object
with `cpusubtype = 0x80000002` only shows `CPU_SUBTYPE_ARM64E (0x2)`.

Add `CpuCapabilities: 0x80` immediately after `CpuSubType`. The value is the
high byte, matching the numeric `caps` column in `llvm-objdump --macho
--private-header --non-verbose`. Print zero as well, and preserve unknown
bits without assigning architecture-specific symbolic names.

The YAML regression test covers ARM64e capability/ABI version bits, unknown
bits, zero capabilities, and 32/64-bit headers in both byte orders. Existing
consecutive header checks are updated for the additional output line.

Validation with a Release + assertions build, X86 target enabled:

```sh
build/bin/llvm-lit -sv -j 4 llvm/test/tools/llvm-readobj \
  llvm/test/MC/MachO/diff-with-two-sections.s \
  llvm/test/MC/MachO/weakdef.s \
  llvm/test/MC/MachO/zero-space.s
```

269 passed, 84 unsupported, 0 failed. The new regression test was also
confirmed to fail on the unmodified tool because the field was missing.
Changed C++ lines pass clang-format checking. The full LLVM suite was not run.

Fixes #80198.

Assisted-by: OpenAI Codex (implementation, tests, and initial description).


>From 51acaa0889d4beb01477449a215a4f923ba02dab Mon Sep 17 00:00:00 2001
From: Tianyi Chen <hi at tychen.cc>
Date: Mon, 7 Sep 2026 00:18:13 +0800
Subject: [PATCH] [llvm-readobj] Display Mach-O CPU capability bits

Mach-O header dumping masks the capability bits out of cpusubtype when
printing CpuSubType, but does not display those bits separately.

Print the high byte as CpuCapabilities, using the same numeric value as
llvm-objdump's non-verbose caps column. Preserve all bits without assigning
architecture-specific names, and display zero when no capabilities are set.

Cover ARM64e capability and ABI version bits, unknown bits, and 32/64-bit
little- and big-endian headers. Update existing consecutive header checks.

Fixes #80198

Assisted-by: OpenAI Codex
---
 llvm/test/MC/MachO/diff-with-two-sections.s   |  1 +
 llvm/test/MC/MachO/weakdef.s                  |  1 +
 llvm/test/MC/MachO/zero-space.s               |  1 +
 .../llvm-readobj/MachO/cpu-capabilities.yaml  | 61 +++++++++++++++++++
 .../MachO/file-headers-arm64.test             |  1 +
 .../llvm-readobj/MachO/file-headers.test      |  2 +
 llvm/tools/llvm-readobj/MachODumper.cpp       |  2 +
 7 files changed, 69 insertions(+)
 create mode 100644 llvm/test/tools/llvm-readobj/MachO/cpu-capabilities.yaml

diff --git a/llvm/test/MC/MachO/diff-with-two-sections.s b/llvm/test/MC/MachO/diff-with-two-sections.s
index 87a8ad960e4c8..76005c64e71f9 100644
--- a/llvm/test/MC/MachO/diff-with-two-sections.s
+++ b/llvm/test/MC/MachO/diff-with-two-sections.s
@@ -15,6 +15,7 @@ Ltmp4 = Leh_func_begin0-Ltmp3
 // CHECK-NEXT:   Magic: Magic (0xFEEDFACE)
 // CHECK-NEXT:   CpuType: X86 (0x7)
 // CHECK-NEXT:   CpuSubType: CPU_SUBTYPE_I386_ALL (0x3)
+// CHECK-NEXT:   CpuCapabilities: 0x0
 // CHECK-NEXT:   FileType: Relocatable (0x1)
 // CHECK-NEXT:   NumOfLoadCommands: 2
 // CHECK-NEXT:   SizeOfLoadCommands: 208
diff --git a/llvm/test/MC/MachO/weakdef.s b/llvm/test/MC/MachO/weakdef.s
index 200be41cd4c73..1f9fce3911f8f 100644
--- a/llvm/test/MC/MachO/weakdef.s
+++ b/llvm/test/MC/MachO/weakdef.s
@@ -22,6 +22,7 @@ __ZTI3optIbE:
 // CHECK-NEXT:   Magic: Magic (0xFEEDFACE)
 // CHECK-NEXT:   CpuType: X86 (0x7)
 // CHECK-NEXT:   CpuSubType: CPU_SUBTYPE_I386_ALL (0x3)
+// CHECK-NEXT:   CpuCapabilities: 0x0
 // CHECK-NEXT:   FileType: Relocatable (0x1)
 // CHECK-NEXT:   NumOfLoadCommands: 4
 // CHECK-NEXT:   SizeOfLoadCommands: 380
diff --git a/llvm/test/MC/MachO/zero-space.s b/llvm/test/MC/MachO/zero-space.s
index 753bd7851bf0f..7acca8e2c53bf 100644
--- a/llvm/test/MC/MachO/zero-space.s
+++ b/llvm/test/MC/MachO/zero-space.s
@@ -14,6 +14,7 @@
 // CHECK-NEXT:   Magic: Magic64 (0xFEEDFACF)
 // CHECK-NEXT:   CpuType: X86-64 (0x1000007)
 // CHECK-NEXT:   CpuSubType: CPU_SUBTYPE_X86_64_ALL (0x3)
+// CHECK-NEXT:   CpuCapabilities: 0x0
 // CHECK-NEXT:   FileType: Relocatable (0x1)
 // CHECK-NEXT:   NumOfLoadCommands: 2
 // CHECK-NEXT:   SizeOfLoadCommands: 248
diff --git a/llvm/test/tools/llvm-readobj/MachO/cpu-capabilities.yaml b/llvm/test/tools/llvm-readobj/MachO/cpu-capabilities.yaml
new file mode 100644
index 0000000000000..0b799d776ea94
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/MachO/cpu-capabilities.yaml
@@ -0,0 +1,61 @@
+## CPU capabilities occupy the high byte of cpusubtype. Display the whole byte
+## without changing the CPU subtype or interpreting architecture-specific bits.
+
+## ARM64e pointer authentication capability bits, including ABI version bits.
+# RUN: yaml2obj %s -DSUBTYPE=0x80000002 -o %t
+# RUN: llvm-readobj -h %t | FileCheck %s --check-prefix=ARM64E -DCAPS=0x80
+# RUN: yaml2obj %s -DSUBTYPE=0x81000002 -o %t
+# RUN: llvm-readobj -h %t | FileCheck %s --check-prefix=ARM64E -DCAPS=0x81
+# RUN: yaml2obj %s -DSUBTYPE=0xC1000002 -o %t
+# RUN: llvm-readobj -h %t | FileCheck %s --check-prefix=ARM64E -DCAPS=0xC1
+
+## Preserve all capability bits, even when their meaning is unknown.
+# RUN: yaml2obj %s -DSUBTYPE=0xFF000002 -o %t
+# RUN: llvm-readobj -h %t | FileCheck %s --check-prefix=ARM64E -DCAPS=0xFF
+# RUN: yaml2obj %s -DSUBTYPE=0x00000002 -o %t
+# RUN: llvm-readobj -h %t | FileCheck %s --check-prefix=ARM64E -DCAPS=0x0
+
+# ARM64E:      CpuSubType: CPU_SUBTYPE_ARM64E (0x2)
+# ARM64E-NEXT: CpuCapabilities: [[CAPS]]{{$}}
+# ARM64E-NEXT: FileType: Relocatable (0x1)
+
+## Also check 32-bit little-endian and 32/64-bit big-endian headers.
+# RUN: yaml2obj %s --docnum=2 -DCPUTYPE=0x7 -DSUBTYPE=0x80000003 -o %t
+# RUN: llvm-readobj -h %t | FileCheck %s --check-prefix=X86
+
+# X86:      CpuSubType: CPU_SUBTYPE_I386_ALL (0x3)
+# X86-NEXT: CpuCapabilities: 0x80{{$}}
+# X86-NEXT: FileType: Relocatable (0x1)
+
+# RUN: yaml2obj %s --docnum=2 -DENDIAN=false -DCPUTYPE=0x12 -DSUBTYPE=0xA5000000 -o %t
+# RUN: llvm-readobj -h %t | FileCheck %s --check-prefixes=PPC,PPC32
+# RUN: yaml2obj %s -DENDIAN=false -DCPUTYPE=0x1000012 -DSUBTYPE=0xA5000000 -o %t
+# RUN: llvm-readobj -h %t | FileCheck %s --check-prefixes=PPC,PPC64
+
+# PPC32:    CpuSubType: CPU_SUBTYPE_POWERPC_ALL (0x0)
+# PPC64:    CpuSubType: 0x0
+# PPC-NEXT: CpuCapabilities: 0xA5{{$}}
+# PPC-NEXT: FileType: Relocatable (0x1)
+
+--- !mach-o
+IsLittleEndian: [[ENDIAN=true]]
+FileHeader:
+  magic:      0xFEEDFACF
+  cputype:    [[CPUTYPE=0x100000C]]
+  cpusubtype: [[SUBTYPE]]
+  filetype:   0x1
+  ncmds:      0
+  sizeofcmds: 0
+  flags:      0
+  reserved:   0
+
+--- !mach-o
+IsLittleEndian: [[ENDIAN=true]]
+FileHeader:
+  magic:      0xFEEDFACE
+  cputype:    [[CPUTYPE]]
+  cpusubtype: [[SUBTYPE]]
+  filetype:   0x1
+  ncmds:      0
+  sizeofcmds: 0
+  flags:      0
diff --git a/llvm/test/tools/llvm-readobj/MachO/file-headers-arm64.test b/llvm/test/tools/llvm-readobj/MachO/file-headers-arm64.test
index 910ee019f3293..5d97647b6654f 100644
--- a/llvm/test/tools/llvm-readobj/MachO/file-headers-arm64.test
+++ b/llvm/test/tools/llvm-readobj/MachO/file-headers-arm64.test
@@ -21,6 +21,7 @@
 # ARM64-NEXT:  Magic: Magic64 (0xFEEDFACF)
 # ARM64-NEXT:  CpuType: Arm64 (0x100000C)
 # ARM64-NEXT:  CpuSubType: [[SUBTYPE]]
+# ARM64-NEXT:  CpuCapabilities: 0x0
 # ARM64-NEXT:  FileType: Relocatable (0x1)
 # ARM64-NEXT:  NumOfLoadCommands: 0
 # ARM64-NEXT:  SizeOfLoadCommands: 0
diff --git a/llvm/test/tools/llvm-readobj/MachO/file-headers.test b/llvm/test/tools/llvm-readobj/MachO/file-headers.test
index d3294f5727e11..0bf6cb6bc84ec 100644
--- a/llvm/test/tools/llvm-readobj/MachO/file-headers.test
+++ b/llvm/test/tools/llvm-readobj/MachO/file-headers.test
@@ -41,6 +41,7 @@ FileHeader:
 # MACHO32-NEXT:  Magic: Magic (0xFEEDFACE)
 # MACHO32-NEXT:  CpuType: [[CPUTYPE]]
 # MACHO32-NEXT:  CpuSubType: [[SUBTYPE]]
+# MACHO32-NEXT:  CpuCapabilities: 0x0
 # MACHO32-NEXT:  FileType: Relocatable (0x1)
 # MACHO32-NEXT:  NumOfLoadCommands: 0
 # MACHO32-NEXT:  SizeOfLoadCommands: 0
@@ -88,6 +89,7 @@ FileHeader:
 # MACHO64-NEXT:  Magic: Magic64 (0xFEEDFACF)
 # MACHO64-NEXT:  CpuType: [[CPUTYPE]]
 # MACHO64-NEXT:  CpuSubType: [[SUBTYPE]]
+# MACHO64-NEXT:  CpuCapabilities: 0x0
 # MACHO64-NEXT:  FileType: Relocatable (0x1)
 # MACHO64-NEXT:  NumOfLoadCommands: 0
 # MACHO64-NEXT:  SizeOfLoadCommands: 0
diff --git a/llvm/tools/llvm-readobj/MachODumper.cpp b/llvm/tools/llvm-readobj/MachODumper.cpp
index a288fbc85058f..8fc92850606af 100644
--- a/llvm/tools/llvm-readobj/MachODumper.cpp
+++ b/llvm/tools/llvm-readobj/MachODumper.cpp
@@ -491,6 +491,8 @@ void MachODumper::printFileHeaders(const MachHeader &Header) {
   default:
     W.printHex("CpuSubType", subtype);
   }
+  W.printHex("CpuCapabilities",
+             (Header.cpusubtype & MachO::CPU_SUBTYPE_MASK) >> 24);
   W.printEnum("FileType", Header.filetype, EnumStrings(MachOHeaderFileTypes));
   W.printNumber("NumOfLoadCommands", Header.ncmds);
   W.printNumber("SizeOfLoadCommands", Header.sizeofcmds);



More information about the llvm-commits mailing list