[GH-ISSUE #7208] [Bug] Ghidra: "Required Objective-C data type not found … Try adding libobjc.dylib" on macOS Mach-O gives callers no actionable recovery #4300

Closed
opened 2026-10-31 00:48:41 +00:00 by bsakaguchi · 2 comments
Owner

Originally created by @kaoru0822-kitauji on GitHub (Oct 26, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/7208

Originally assigned to: @kaoru0822-kitauji on GitHub.

Before submitting

  • I searched the open and closed issues for a related report. Related: #1783 (closed; a different Objective-C Message Analyzer failure, a stall), #1048 (large-target startup deadline).
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Native analysis or decompilation (Ghidra provider, macOS Mach-O)

Observed behavior

A user on Discord reports that analyzing a macOS game executable (Transport Fever 3) with the Ghidra provider fails, and the output they see is:

ERROR: Required Objective-C data type not found in data type manager
Try adding libobjc.dylib

The binary links /usr/lib/libobjc.A.dylib. On modern macOS that library exists only in the dyld shared cache, not on disk, and REA has no setting for "adding" a library, so the user is stuck. We do not have the user's full REA result, so we don't know its failure_kind or whether the session actually failed or just logged this line.

Source-derived (not run against real Ghidra; it isn't installed here): this line is non-fatal Ghidra auto-analysis output. Adding libobjc would not help in REA's headless import.

  1. ObjcMessageAnalyzer.getDataTypes (Ghidra 12.1.4: Ghidra/Features/Decompiler/.../ObjcMessageAnalyzer.java:206-207; 12.2/master: Features/Objective-C/...:207-208) looks up /_objc2_/ID, /_objc2_/SEL and /_objc2_/class_t in the program's own data type manager. If any are missing, it logs these two lines and added() returns false.
  2. Only the program's own Objective-C metadata creates those types. Objective-C Type Metadata Analyzer (FORMAT_ANALYSIS, runs earlier) creates class_t when it marks up a class from __objc_classlist (Objc2Class.NAME). It creates ID/SEL only as typedefs while decoding method type encodings (Objc1TypeEncodings.createProperDataType). Any exception during that markup is also caught and dropped silently (ObjcTypeMetadataAnalyzer.added: catch (Exception e) { return false; }).
  3. ObjcMessageAnalyzer.canAnalyze only requires a __objc_* block plus __objc_stubs or an external _objc_msgSend* symbol. So a program that calls Objective-C but defines no classes or methods enables the analyzer without ever creating its types. A C++ engine with thin Cocoa glue is a common case of that. The same happens to a program whose metadata markup throws.
  4. AnalysisScheduler only ORs added()'s return value into its "changed" result (result |= analyzer.added(...)). Analysis continues. The analyzer simply skips objc_msgSend call-target recovery.
  5. Loading libobjc.A.dylib as a library would create a separate program; it would not add these types to the target's data type manager. REA always imports into an ephemeral project (-import … -readOnly -deleteProject) with default loader options anyway.

So if the user's session did fail, the cause is most likely something else, and this line is just the most visible one in the Ghidra diagnostics. For a large game executable, a likely candidate is the import plus auto-analysis startup deadline (#1048). That is a hypothesis; the user's failure_kind is needed to confirm it.

Reproduction

Partial: the binary structure was checked; Ghidra was not run.

cat > noclass.m <<'EOF'
#import <Foundation/Foundation.h>
int main(int argc, const char **argv) {
  @autoreleasepool {
    NSString *s = [NSString stringWithUTF8String:argv[0]];
    NSLog(@"%@ %lu", s, (unsigned long)[s length]);
  }
  return 0;
}
EOF
clang -O1 -fobjc-arc -framework Foundation noclass.m -o noclass
otool -l noclass | grep sectname | grep objc

Observed (Apple clang 21.0.0, macOS 26.7.1, arm64):

  • noclass: __objc_imageinfo, __objc_selrefs, __objc_methname, __objc_stubs. There is no __objc_classlist or __objc_methtype, so by (2)–(3) the message analyzer runs but ID/SEL/class_t are never created. Expected to log the reported lines.
  • Control withclass (the same program plus one @implementation Greeter : NSObject with one method): adds __objc_classlist, __objc_methlist and __objc_methtype (@24@0:8@16). Expected not to log them.
  • ls /usr/lib/libobjc.A.dylib → No such file or directory, although both binaries link it.

Proposed real-provider check: run rea function ./noclass main --json and the same for ./withclass with Ghidra 12.1.4. Confirm that the line appears only for noclass, that both sessions open and answer, and record analysis_complete.

Expected behavior

  • The session opens and answers. The missing Objective-C call-target recovery is the only effect (expected per the source above; unverified).
  • A caller who sees this line can find out that it is benign and what to do instead. Today the only text they get is Ghidra's advice, which doesn't apply to REA.

Impact and scope

The user can't tell what failed or what to change. They may go looking for a libobjc file that doesn't exist on disk. Whether REA itself mis-classifies anything is unknown: we need the user's full result.

Proposed fix

  1. Docs (small, do first): add a troubleshooting entry next to the REA_GHIDRA_DISABLED_ANALYZERS section in docs/installation.md. Say that the line is Ghidra analyzer output and not an REA failure, that there is no libobjc to add, and to check the session's failure_kind for the real cause. To silence the analyzer, set REA_GHIDRA_DISABLED_ANALYZERS="Objective-C Message Analyzer". For large executables, raise REA_GHIDRA_STARTUP_TIMEOUT_MS and the Ghidra heap.
  2. Structured fact instead of log text (needs real Ghidra to verify): after analysis, the bridge can check for the same three /_objc2_ types when the program is Objective-C 2 and the analyzer is enabled. If they are missing, it reports a limitation such as "Objective-C message-send targets were not resolved: the program defines no Objective-C class metadata". That is a typed fact from the program, not a parse of Ghidra's log, so it survives Ghidra changing its wording.
  3. Upstream (optional): suggest to Ghidra that the analyzer create the /_objc2_ basics itself, or not enable when the program has no class metadata, and drop the "Try adding libobjc.dylib" advice.

We are not proposing to create the /_objc2_ types in a pre-script. That would conflict with the types the metadata analyzer creates, and it would turn on a parallel decompile of every function. #1783 shows that path can stall for a long time on large programs.

REA version

main at b3818bbba (source analysis)

Runtime and platform

Repro build: macOS 26.7.1 arm64, Apple clang 21.0.0. Ghidra not installed here. The user's REA, Ghidra and JDK versions: NOT_ENOUGH_INFO.

Target and artifact context

Thin or universal Mach-O game executable linking libobjc.A.dylib. The user's slice, size and architecture: NOT_ENOUGH_INFO.

Earliest failure stage

Unknown. The full REA result (failure_kind, analysis_complete) is needed.

Relevant output or evidence

  • Ghidra source at tag Ghidra_12.1.4_build: Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/analysis/ObjcMessageAnalyzer.java (getDataTypes, canAnalyze).
  • Ghidra master: Ghidra/Features/Objective-C/src/main/java/ghidra/app/plugin/core/analysis/ObjcTypeMetadataAnalyzer.java, .../objc/objc1/Objc1TypeEncodings.java, .../objc/objc2/Objc2Constants.java.
  • Ghidra 12.1.4 AnalysisScheduler.java: result |= analyzer.added(...).
  • The local otool -l section lists above.
Originally created by @kaoru0822-kitauji on GitHub (Oct 26, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7208 Originally assigned to: @kaoru0822-kitauji on GitHub. ### Before submitting - [x] I searched the open and closed issues for a related report. Related: #1783 (closed; a different `Objective-C Message Analyzer` failure, a stall), #1048 (large-target startup deadline). - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Native analysis or decompilation (Ghidra provider, macOS Mach-O) ### Observed behavior A user on Discord reports that analyzing a macOS game executable (Transport Fever 3) with the Ghidra provider fails, and the output they see is: ``` ERROR: Required Objective-C data type not found in data type manager Try adding libobjc.dylib ``` The binary links `/usr/lib/libobjc.A.dylib`. On modern macOS that library exists only in the dyld shared cache, not on disk, and REA has no setting for "adding" a library, so the user is stuck. We do **not** have the user's full REA result, so we don't know its `failure_kind` or whether the session actually failed or just logged this line. **Source-derived (not run against real Ghidra; it isn't installed here):** this line is non-fatal Ghidra auto-analysis output. Adding libobjc would not help in REA's headless import. 1. `ObjcMessageAnalyzer.getDataTypes` (Ghidra 12.1.4: `Ghidra/Features/Decompiler/.../ObjcMessageAnalyzer.java:206-207`; 12.2/master: `Features/Objective-C/...:207-208`) looks up `/_objc2_/ID`, `/_objc2_/SEL` and `/_objc2_/class_t` in the **program's own** data type manager. If any are missing, it logs these two lines and `added()` returns `false`. 2. Only the program's own Objective-C metadata creates those types. `Objective-C Type Metadata Analyzer` (FORMAT_ANALYSIS, runs earlier) creates `class_t` when it marks up a class from `__objc_classlist` (`Objc2Class.NAME`). It creates `ID`/`SEL` only as typedefs while decoding method type encodings (`Objc1TypeEncodings.createProperDataType`). Any exception during that markup is also caught and dropped silently (`ObjcTypeMetadataAnalyzer.added`: `catch (Exception e) { return false; }`). 3. `ObjcMessageAnalyzer.canAnalyze` only requires a `__objc_*` block plus `__objc_stubs` or an external `_objc_msgSend*` symbol. So a program that *calls* Objective-C but *defines* no classes or methods enables the analyzer without ever creating its types. A C++ engine with thin Cocoa glue is a common case of that. The same happens to a program whose metadata markup throws. 4. `AnalysisScheduler` only ORs `added()`'s return value into its "changed" result (`result |= analyzer.added(...)`). Analysis continues. The analyzer simply skips `objc_msgSend` call-target recovery. 5. Loading `libobjc.A.dylib` as a library would create a separate program; it would not add these types to the target's data type manager. REA always imports into an ephemeral project (`-import … -readOnly -deleteProject`) with default loader options anyway. So if the user's session did fail, the cause is most likely something else, and this line is just the most visible one in the Ghidra diagnostics. For a large game executable, a likely candidate is the import plus auto-analysis startup deadline (#1048). That is a hypothesis; the user's `failure_kind` is needed to confirm it. ### Reproduction Partial: the binary structure was checked; Ghidra was **not** run. ```bash cat > noclass.m <<'EOF' #import <Foundation/Foundation.h> int main(int argc, const char **argv) { @autoreleasepool { NSString *s = [NSString stringWithUTF8String:argv[0]]; NSLog(@"%@ %lu", s, (unsigned long)[s length]); } return 0; } EOF clang -O1 -fobjc-arc -framework Foundation noclass.m -o noclass otool -l noclass | grep sectname | grep objc ``` Observed (Apple clang 21.0.0, macOS 26.7.1, arm64): - `noclass`: `__objc_imageinfo`, `__objc_selrefs`, `__objc_methname`, `__objc_stubs`. There is no `__objc_classlist` or `__objc_methtype`, so by (2)–(3) the message analyzer runs but `ID`/`SEL`/`class_t` are never created. Expected to log the reported lines. - Control `withclass` (the same program plus one `@implementation Greeter : NSObject` with one method): adds `__objc_classlist`, `__objc_methlist` and `__objc_methtype` (`@24@0:8@16`). Expected not to log them. - `ls /usr/lib/libobjc.A.dylib` → `No such file or directory`, although both binaries link it. Proposed real-provider check: run `rea function ./noclass main --json` and the same for `./withclass` with Ghidra 12.1.4. Confirm that the line appears only for `noclass`, that both sessions open and answer, and record `analysis_complete`. ### Expected behavior - The session opens and answers. The missing Objective-C call-target recovery is the only effect (expected per the source above; unverified). - A caller who sees this line can find out that it is benign and what to do instead. Today the only text they get is Ghidra's advice, which doesn't apply to REA. ### Impact and scope The user can't tell what failed or what to change. They may go looking for a libobjc file that doesn't exist on disk. Whether REA itself mis-classifies anything is unknown: we need the user's full result. ### Proposed fix 1. **Docs (small, do first):** add a troubleshooting entry next to the `REA_GHIDRA_DISABLED_ANALYZERS` section in `docs/installation.md`. Say that the line is Ghidra analyzer output and not an REA failure, that there is no libobjc to add, and to check the session's `failure_kind` for the real cause. To silence the analyzer, set `REA_GHIDRA_DISABLED_ANALYZERS="Objective-C Message Analyzer"`. For large executables, raise `REA_GHIDRA_STARTUP_TIMEOUT_MS` and the Ghidra heap. 2. **Structured fact instead of log text (needs real Ghidra to verify):** after analysis, the bridge can check for the same three `/_objc2_` types when the program is Objective-C 2 and the analyzer is enabled. If they are missing, it reports a limitation such as "Objective-C message-send targets were not resolved: the program defines no Objective-C class metadata". That is a typed fact from the program, not a parse of Ghidra's log, so it survives Ghidra changing its wording. 3. **Upstream (optional):** suggest to Ghidra that the analyzer create the `/_objc2_` basics itself, or not enable when the program has no class metadata, and drop the "Try adding libobjc.dylib" advice. We are not proposing to create the `/_objc2_` types in a pre-script. That would conflict with the types the metadata analyzer creates, and it would turn on a parallel decompile of every function. #1783 shows that path can stall for a long time on large programs. ### REA version main at b3818bbba (source analysis) ### Runtime and platform Repro build: macOS 26.7.1 arm64, Apple clang 21.0.0. Ghidra not installed here. The user's REA, Ghidra and JDK versions: NOT_ENOUGH_INFO. ### Target and artifact context Thin or universal Mach-O game executable linking `libobjc.A.dylib`. The user's slice, size and architecture: NOT_ENOUGH_INFO. ### Earliest failure stage Unknown. The full REA result (`failure_kind`, `analysis_complete`) is needed. ### Relevant output or evidence - Ghidra source at tag `Ghidra_12.1.4_build`: `Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/analysis/ObjcMessageAnalyzer.java` (`getDataTypes`, `canAnalyze`). - Ghidra master: `Ghidra/Features/Objective-C/src/main/java/ghidra/app/plugin/core/analysis/ObjcTypeMetadataAnalyzer.java`, `.../objc/objc1/Objc1TypeEncodings.java`, `.../objc/objc2/Objc2Constants.java`. - Ghidra 12.1.4 `AnalysisScheduler.java`: `result |= analyzer.added(...)`. - The local `otool -l` section lists above.
bsakaguchi 2026-10-31 00:48:41 +00:00
  • closed this issue
  • added the
    bug
    label
Author
Owner

@XinwangTao commented on GitHub (Oct 26, 2026):

这个分析很扎实,尤其是第 5 点——加载 libobjc.A.dylib 只会生成一个独立 program,并不会把 /objc2 类型注入目标程序的数据类型管理器,这直接说明 Ghidra 的建议在 REA 的 headless 导入场景下确实不可操作,用户照着去找 libobjc 文件只会白费功夫。

补充一个思路供参考:proposed fix 的第 2 点(结构化事实)其实可以不依赖 Ghidra 侧——REA 在 pre-analysis 阶段就能从 Mach-O 的 LC_LOAD_DYLIB 看出目标链接了 libobjc,同时检查 section 里有没有 __objc_classlist;两者组合(链接了 ObjC 运行时 + 无 class metadata)即可提前产出同样的 typed limitation,不用等 Ghidra 跑完再解析它的日志。文档条目先上是合理的,建议在 troubleshooting 里顺带注明现代 macOS 上 /usr/lib/libobjc.A.dylib 只在 dyld shared cache 中存在这一事实,避免用户去文件系统里找。

<!-- gh-comment-id:6411168680 --> @XinwangTao commented on GitHub (Oct 26, 2026): 这个分析很扎实,尤其是第 5 点——加载 libobjc.A.dylib 只会生成一个独立 program,并不会把 /_objc2_ 类型注入目标程序的数据类型管理器,这直接说明 Ghidra 的建议在 REA 的 headless 导入场景下确实不可操作,用户照着去找 libobjc 文件只会白费功夫。 补充一个思路供参考:proposed fix 的第 2 点(结构化事实)其实可以不依赖 Ghidra 侧——REA 在 pre-analysis 阶段就能从 Mach-O 的 LC_LOAD_DYLIB 看出目标链接了 libobjc,同时检查 section 里有没有 __objc_classlist;两者组合(链接了 ObjC 运行时 + 无 class metadata)即可提前产出同样的 typed limitation,不用等 Ghidra 跑完再解析它的日志。文档条目先上是合理的,建议在 troubleshooting 里顺带注明现代 macOS 上 /usr/lib/libobjc.A.dylib 只在 dyld shared cache 中存在这一事实,避免用户去文件系统里找。
Author
Owner

@XinwangTao commented on GitHub (Oct 28, 2026):

The #7249 doc clarification is exactly the right first fix — with libobjc.A.dylib living only in the dyld shared cache on modern macOS, telling users to "add libobjc.dylib" was a dead end, and the troubleshooting note now makes the limitation actionable instead. Worth keeping the earlier suggestion on the table as a follow-up: detect at pre-analysis time (LC_LOAD_DYLIB shows libobjc linked, and no __objc_classlist section) and emit the typed limitation before Ghidra ever runs, so callers get a structured fact rather than a log line.

<!-- gh-comment-id:6457972572 --> @XinwangTao commented on GitHub (Oct 28, 2026): The #7249 doc clarification is exactly the right first fix — with libobjc.A.dylib living only in the dyld shared cache on modern macOS, telling users to "add libobjc.dylib" was a dead end, and the troubleshooting note now makes the limitation actionable instead. Worth keeping the earlier suggestion on the table as a follow-up: detect at pre-analysis time (LC_LOAD_DYLIB shows libobjc linked, and no __objc_classlist section) and emit the typed limitation before Ghidra ever runs, so callers get a structured fact rather than a log line.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#4300
No description provided.