Disassemble macOS binaries to check for SME and SME2 instructions
Introduction
Prepare an arm64 Mach-O fixture
Inspect strings and symbols in a binary
Disassemble binaries and verify SME2
Automate and adapt the inspection workflow
Next Steps
Disassemble macOS binaries to check for SME and SME2 instructions
Build the SME2 fixture and comparison binaries
A macOS binary can contain Scalable Matrix Extension (SME) and SME2 instructions. Including supporting source code or enabling compiler options doesn’t mean that those instructions are present in the compiled binary.
You’ll build a small Mach-O executable and inspect its final machine code for SME and SME2 instructions. You’ll identify state-control, matrix, and predicate instructions. You’ll correlate each finding with its exact four bytes and decoded mnemonic. You’ll also inspect the operands, static program counter (PC), symbol, and surrounding code.
You can use the same inspection method to check for other instruction groups in a compiled binary.
Create an example binary containing SME2 instructions
You’ll begin by creating an example program from source code. This program will contain an SME2 function, a normal C entry point, and a feature-name string.
The example contains SME and SME2 assembly instructions and is compiled for macOS on Apple silicon. You’ll compile the code but never run it, so you don’t need SME2 hardware on your Mac.
To begin, go to your home directory, create a working directory, and navigate to it:
cd "$HOME"
mkdir arm64-extension-inspection
cd arm64-extension-inspection
If you open a new terminal or return to the project later, run:
cd "$HOME/arm64-extension-inspection"
Create sme2-main.c:
cat > sme2-main.c <<'EOF'
#include <stddef.h>
extern void sme2_fixture_kernel(void);
__attribute__((used))
static const char feature_hint[] = "hw.optional.arm.FEAT_SME2";
void (*volatile keep_sme2_kernel)(void) = sme2_fixture_kernel;
int main(void) {
return keep_sme2_kernel == NULL || feature_hint[0] == '\0';
}
EOF
Next, create sme2-kernel.S:
cat > sme2-kernel.S <<'EOF'
.text
.p2align 2
.globl _sme2_fixture_kernel
_sme2_fixture_kernel:
smstart
ptrue p0.s
ptrue pn8.s
whilelt pn8.s, x0, x0, vlx2
zero { za }
fmopa za0.s, p0/m, p0/m, z0.s, z1.s
smstop
ret
EOF
Build the example program:
xcrun clang -arch arm64 -O2 -march=armv9-a+sme2 \
sme2-main.c sme2-kernel.S -o sme2-positive
The -march=armv9-a+sme2 option enables SME2 instructions during compilation. You’ll inspect the compiled binary to confirm that they’re present.
You now have a compiled example program ready for inspection.
Create negative controls
The compiled example program serves as your positive control. Create string-only-control.c as a negative control with a feature string and a symbol name similar to SME2, but no SME2 instructions:
cat > string-only-control.c <<'EOF'
__attribute__((used))
static const char feature_hint[] = "hw.optional.arm.FEAT_SME2";
__attribute__((noinline, used))
void sme2_string_only_marker(void) {
__asm__ volatile("" ::: "memory");
}
int main(void) {
return feature_hint[0] == '\0';
}
EOF
Create another comparison program, plain-arm64-control.c, without SME or SME2 code, names, or strings:
cat > plain-arm64-control.c <<'EOF'
__attribute__((noinline))
int arm64_plain_add(int left, int right) {
return left + right;
}
int main(void) {
return arm64_plain_add(1, 2) == 3 ? 0 : 1;
}
EOF
Build both controls for the base arm64 architecture:
xcrun clang -arch arm64 -O2 string-only-control.c -o string-only-control
xcrun clang -arch arm64 -O2 plain-arm64-control.c -o plain-arm64-control
Confirm the final architecture slice
With your controls built, begin with file-type and architecture-slice checks before inspecting the instructions.
A thin Mach-O file contains one architecture slice. A universal Mach-O file, also called a fat Mach-O file, can contain several slices, including arm64 and x86_64. A universal file can therefore contain both x86 and Arm executable code.
Use file and lipo to identify the file type and slices.
Inspect the three final linked executables:
file sme2-positive string-only-control plain-arm64-control
lipo -info sme2-positive
The output is similar to:
sme2-positive: Mach-O 64-bit executable arm64
string-only-control: Mach-O 64-bit executable arm64
plain-arm64-control: Mach-O 64-bit executable arm64
Non-fat file: sme2-positive is architecture: arm64
For a universal binary, lipo -info might report both x86_64 and arm64. The later -arch arm64 options select the slice to inspect.
If you inspect a universal binary, ensure that you’re working with the arm64 slice.
The source code that you’ll examine to complete this Learning Path produces a thin Mach-O binary.
What you’ve accomplished and what’s next
You’ve built an example binary containing SME2 instructions and two comparison binaries without them.
Next, you’ll inspect strings and symbols to see why they don’t prove instructions are present.