[GH-ISSUE #1658] [Bug] A function is cut at a wrongly flagged no-return call (malloc, __tls_get_addr) and REA gives no warning #560

Closed
opened 2026-10-15 18:26:10 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @CassieFox23 on GitHub (Oct 11, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/1658

TL;DR: On a stripped Rust library, Ghidra treats two libc calls as calls that never return. That cuts one function from 78 instructions to 8, and REA shows it with no warning. REA already has a script that undoes this for some functions, but it skips these two.

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Native analysis or decompilation

Observed behavior

I pointed REA at loot.cpython-313-x86_64-linux-gnu.so with the Ghidra provider. The file is the Python module of libloot 0.29.6 (GPL-3.0-or-later). It ships inside Amethyst Mod Manager 2.5.3 (GPL-3.0-or-later). It is Rust with PyO3 0.25.1, and it is stripped.

The one exported function is PyInit_loot. REA returns this for it (the three try/catch comment lines are removed):

void PyInit_loot(void)
{
  /* WARNING: Subroutine does not return */
  __tls_get_addr(&PTR_0068ee28);
}

The assembly view stops at the same call, after 8 instructions. The procedure info says the body is 43 bytes. The function dossier returns the same body. It reports confidence: observed and only the generic limitations.

The real function is longer. In objdump -d it runs from file offset 0x3e1020 to a ud2 at 0x3e1172. That range holds 78 instructions. The code reads a thread-local counter, calls the module init, and builds the Python error on failure.

Ghidra alone gives the same result. I ran analyzeHeadless on the same file with default analysis and no REA script. The function has the same 8 instructions. The external __tls_get_addr carries the no-return flag. So Ghidra sets the flag. REA does not clear it, and it does not say the body may be cut.

The flag is wrong for more than one call. These are Ghidra's counts after default analysis, before any REA script ran:

file       call              really no-return?  sites  functions
loot       malloc            no                 1,611  840
loot       __tls_get_addr    no                 197    156
loot       _Unwind_Resume    yes                636    189
loot       abort             yes                1      1
filegraph  memcpy            no                 1,206  482
filegraph  memset            no                 187    117
filegraph  strlen            no                 217    102
filegraph  __errno_location  no                 87     45

"Sites" are the calls where Ghidra ends the flow. "Functions" is how many functions hold them. I did not measure how many of those functions are really cut. A call can end the flow while the code after it is reached some other way. PyInit_loot is cut. PyInit_amethyst_filegraph is not cut, and it decompiles in full.

Reproduction

  1. Get the file from Amethyst Mod Manager 2.5.3. The v2.5.3 release has the Flatpak as AmethystModManager.flatpak. The file sits at /app/lib/python3.13/site-packages/loot.cpython-313-x86_64-linux-gnu.so inside it.
  2. I took mine from an installed copy. I did not compare the release asset byte for byte (unverified). The SHA-256 of my file is 7777474017b65531258cc1678f2c1ee45e61f4b76de4890c02c7436cbbbe60a8.
  3. Copy the file to a plain folder.
  4. Run rea decompile /abs/path/loot.cpython-313-x86_64-linux-gnu.so PyInit_loot --provider ghidra --format json. Over MCP, call open_binary with provider_id set to ghidra. Then call procedure_pseudo_code for PyInit_loot.
  5. Compare with objdump -d --start-address=0x3e1020 --stop-address=0x3e1180 on the same file.

The CLI command and the MCP route gave the same result on main.

The first import took 4 to 5 minutes here, with three or four Ghidra processes running. After open_binary, later calls on the same MCP session answered in under a second.

A small Rust library I built myself did not reproduce this. In it, __tls_get_addr was not flagged. The function decompiled in full, with 48 instructions. So the Amethyst file is needed. I do not know what makes Ghidra set the flag on this file.

Expected behavior

PyInit_loot should come back whole. If Ghidra's flag cannot be trusted, the result should say so. Today nothing tells the reader that the body may be cut at a no-return call.

Impact and scope

  • Affected: the pseudo-code, the assembly, the info and the dossier. Any function that calls such an external can be hit.
  • Not affected: the bytes. read_bytes at 0x4e1046 returns the call and the code after it. inspect_binary_layout is also untouched.
  • Evidence integrity: the result is typed observed and carries no limitation. A caller reads PyInit_loot as a function that never returns.
  • Seen on two Rust files. I have not tried C or C++ files that make the same libc calls.

REA version

Main branch, at commit 4999e4be2 (#1651). Its package.json says 6.3.0. The released 6.3.0 package gave the same output in an earlier run. I did not re-run it today.

Main has moved to 639b73097 since then. The no-return script has not changed there. I checked with git diff.

Runtime and platform

Node 24.18.0; Nobara 44 (Fedora 44 base), Linux 7.2.9, x86-64; Ghidra 12.1.4 (Apache-2.0); JDK 21.0.12.1

Target and artifact context

ELF64 x86-64 shared object, stripped, Rust with PyO3 0.25.1, 5,836,952 bytes. A second file, amethyst_filegraph.abi3.so, was used for the filegraph rows.

Interface

MCP

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

The decompile and the table are above. These are the first lines of the real function, from objdump -d:

3e1020: push   %r14
3e1022: push   %rbx
3e1023: sub    $0xb8,%rsp
3e102a: lea    -0x30aea9(%rip),%rax
3e1031: mov    %rax,0x8(%rsp)
3e1036: movq   $0x1e,0x10(%rsp)
3e103f: lea    0x1adde2(%rip),%rdi
3e1046: call   529080 <__tls_get_addr@plt>
3e104b: mov    0x70(%rax),%rdi      <- Ghidra stops before this
...
3e1102: ret

In this file, Ghidra adds 0x100000 to file offsets. The call is at 0x4e1046 in REA.

Typed outcome or recovery expected

When a function body ends at a call whose callee has the no-return flag, the result could carry a limitation. It would name the callee.

Analysis or suspected cause

This part is read from source, and I tested the fix idea once.

The script bridge/ghidra/ReaGhidraNoReturnFix.java at 4999e4be2 already clears wrong no-return flags. Its rule is: flagged no-return, and the body holds a real return. Its loop skips thunks, which covers the PLT stubs. An external function has no body with a return in it. So neither one is ever cleared.

After the script ran on loot, it reported cleared=4 sites=75 refit=23. The external __tls_get_addr stayed flagged on all 197 call sites. The external malloc stayed flagged on all 1,611.

I tried a copy of that script that also clears the flag on six externals and their stubs. The six are malloc, __tls_get_addr, memcpy, memset, strlen and __errno_location. The copy then re-disassembles the call sites the same way. After that, the function came back with 59 instructions. The decompile has 74 lines. It now shows the real calls and PyErr_SetRaisedException.

That copy re-disassembled 1,812 call sites and refit 994 functions in loot. I did not check whether those refits are right (unverified). I tried it on one file.

Commands
export GHIDRA_INSTALL_DIR=/path/to/ghidra_12.1.4_PUBLIC JAVA_HOME=/path/to/jdk-21
export REA_GHIDRA_STARTUP_TIMEOUT_MS=600000

# REA
node scripts/rea.mjs decompile /abs/loot.cpython-313-x86_64-linux-gnu.so PyInit_loot --provider ghidra --format json

# Ghidra alone, with a script that prints the facts
$GHIDRA_INSTALL_DIR/support/analyzeHeadless /tmp/proj rea-project \
  -import /abs/loot.cpython-313-x86_64-linux-gnu.so -readOnly -deleteProject \
  -scriptPath /abs/scripts -postScript P4Dump2.java ghidra_default_analysis_only PyInit_ masterlist

The script P4Dump2.java takes a label, a function name prefix and a list of search terms. It lists the instructions of the matching function. It also prints the no-return flag of each call target and the decompiler text. The script P4Nr.java counts call sites per no-return external. I can share both scripts if that helps. The fix idea from the last section is this diff against the REA script:

--- bridge/ghidra/ReaGhidraNoReturnFix.java (4999e4be2)
+++ P4NoReturnExt.java (class renamed; run after the original script)
@@ -16,7 +16,7 @@
  * call as a terminator and the decompiler truncated them. Clear the flag, then re-disassemble only
  * the call sites of the functions that were cleared and refit their owning functions in place.
  */
-public final class ReaGhidraNoReturnFix extends GhidraScript {
+public final class P4NoReturnExt extends GhidraScript {
   @Override
   public void run() throws Exception {
     // Windows P0 does not admit analysis-database mutation.
@@ -24,6 +24,21 @@
     FunctionManager fm = currentProgram.getFunctionManager();
     Listing li = currentProgram.getListing();
     Set<Address> cleared = new HashSet<>();
+    Set<String> names = Set.of("__tls_get_addr", "malloc", "memcpy", "memset", "strlen", "__errno_location");
+    for (Function e : fm.getExternalFunctions()) {
+      if (names.contains(e.getName()) && e.hasNoReturn()) {
+        e.setNoReturn(false);
+        println("P4X cleared external " + e.getName());
+      }
+    }
+    for (Function t : fm.getFunctions(true)) {
+      if (!t.isThunk()) continue;
+      Function target = t.getThunkedFunction(true);
+      if (target != null && names.contains(target.getName())) {
+        if (t.hasNoReturn()) t.setNoReturn(false);
+        cleared.add(t.getEntryPoint());
+      }
+    }
     for (Function f : fm.getFunctions(true)) {
       monitor.checkCancelled();
       if (!f.hasNoReturn() || f.isThunk()) continue;
Test setup

Related, but not the same, is #1244. It asks for a warning when a guessed signature clashes with the code.

🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊

Originally created by @CassieFox23 on GitHub (Oct 11, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/1658 **TL;DR:** On a stripped Rust library, Ghidra treats two libc calls as calls that never return. That cuts one function from 78 instructions to 8, and REA shows it with no warning. REA already has a script that undoes this for some functions, but it skips these two. ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Native analysis or decompilation ### Observed behavior I pointed REA at `loot.cpython-313-x86_64-linux-gnu.so` with the Ghidra provider. The file is the Python module of libloot 0.29.6 (GPL-3.0-or-later). It ships inside [Amethyst Mod Manager](https://github.com/ChrisDKN/Amethyst-Mod-Manager) 2.5.3 (GPL-3.0-or-later). It is Rust with PyO3 0.25.1, and it is stripped. The one exported function is `PyInit_loot`. REA returns this for it (the three try/catch comment lines are removed): ```text void PyInit_loot(void) { /* WARNING: Subroutine does not return */ __tls_get_addr(&PTR_0068ee28); } ``` The assembly view stops at the same call, after 8 instructions. The procedure info says the body is 43 bytes. The function dossier returns the same body. It reports `confidence: observed` and only the generic limitations. The real function is longer. In `objdump -d` it runs from file offset 0x3e1020 to a `ud2` at 0x3e1172. That range holds 78 instructions. The code reads a thread-local counter, calls the module init, and builds the Python error on failure. Ghidra alone gives the same result. I ran `analyzeHeadless` on the same file with default analysis and no REA script. The function has the same 8 instructions. The external `__tls_get_addr` carries the no-return flag. So Ghidra sets the flag. REA does not clear it, and it does not say the body may be cut. The flag is wrong for more than one call. These are Ghidra's counts after default analysis, before any REA script ran: ```text file call really no-return? sites functions loot malloc no 1,611 840 loot __tls_get_addr no 197 156 loot _Unwind_Resume yes 636 189 loot abort yes 1 1 filegraph memcpy no 1,206 482 filegraph memset no 187 117 filegraph strlen no 217 102 filegraph __errno_location no 87 45 ``` "Sites" are the calls where Ghidra ends the flow. "Functions" is how many functions hold them. I did not measure how many of those functions are really cut. A call can end the flow while the code after it is reached some other way. `PyInit_loot` is cut. `PyInit_amethyst_filegraph` is not cut, and it decompiles in full. ### Reproduction 1. Get the file from Amethyst Mod Manager 2.5.3. The [v2.5.3 release](https://github.com/ChrisDKN/Amethyst-Mod-Manager/releases/tag/v2.5.3) has the Flatpak as `AmethystModManager.flatpak`. The file sits at `/app/lib/python3.13/site-packages/loot.cpython-313-x86_64-linux-gnu.so` inside it. 2. I took mine from an installed copy. I did not compare the release asset byte for byte (unverified). The SHA-256 of my file is `7777474017b65531258cc1678f2c1ee45e61f4b76de4890c02c7436cbbbe60a8`. 3. Copy the file to a plain folder. 4. Run `rea decompile /abs/path/loot.cpython-313-x86_64-linux-gnu.so PyInit_loot --provider ghidra --format json`. Over MCP, call `open_binary` with `provider_id` set to `ghidra`. Then call `procedure_pseudo_code` for `PyInit_loot`. 5. Compare with `objdump -d --start-address=0x3e1020 --stop-address=0x3e1180` on the same file. The CLI command and the MCP route gave the same result on main. The first import took 4 to 5 minutes here, with three or four Ghidra processes running. After `open_binary`, later calls on the same MCP session answered in under a second. A small Rust library I built myself did not reproduce this. In it, `__tls_get_addr` was not flagged. The function decompiled in full, with 48 instructions. So the Amethyst file is needed. I do not know what makes Ghidra set the flag on this file. ### Expected behavior `PyInit_loot` should come back whole. If Ghidra's flag cannot be trusted, the result should say so. Today nothing tells the reader that the body may be cut at a no-return call. ### Impact and scope - Affected: the pseudo-code, the assembly, the info and the dossier. Any function that calls such an external can be hit. - Not affected: the bytes. `read_bytes` at 0x4e1046 returns the call and the code after it. `inspect_binary_layout` is also untouched. - Evidence integrity: the result is typed `observed` and carries no limitation. A caller reads `PyInit_loot` as a function that never returns. - Seen on two Rust files. I have not tried C or C++ files that make the same libc calls. ### REA version Main branch, at commit 4999e4be2 (#1651). Its package.json says 6.3.0. The released 6.3.0 package gave the same output in an earlier run. I did not re-run it today. Main has moved to 639b73097 since then. The no-return script has not changed there. I checked with git diff. ### Runtime and platform Node 24.18.0; Nobara 44 (Fedora 44 base), Linux 7.2.9, x86-64; Ghidra 12.1.4 (Apache-2.0); JDK 21.0.12.1 ### Target and artifact context ELF64 x86-64 shared object, stripped, Rust with PyO3 0.25.1, 5,836,952 bytes. A second file, `amethyst_filegraph.abi3.so`, was used for the filegraph rows. ### Interface MCP ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence The decompile and the table are above. These are the first lines of the real function, from `objdump -d`: ```text 3e1020: push %r14 3e1022: push %rbx 3e1023: sub $0xb8,%rsp 3e102a: lea -0x30aea9(%rip),%rax 3e1031: mov %rax,0x8(%rsp) 3e1036: movq $0x1e,0x10(%rsp) 3e103f: lea 0x1adde2(%rip),%rdi 3e1046: call 529080 <__tls_get_addr@plt> 3e104b: mov 0x70(%rax),%rdi <- Ghidra stops before this ... 3e1102: ret ``` In this file, Ghidra adds 0x100000 to file offsets. The call is at 0x4e1046 in REA. ### Typed outcome or recovery expected When a function body ends at a call whose callee has the no-return flag, the result could carry a limitation. It would name the callee. ### Analysis or suspected cause This part is read from source, and I tested the fix idea once. The script `bridge/ghidra/ReaGhidraNoReturnFix.java` at 4999e4be2 already clears wrong no-return flags. Its rule is: flagged no-return, and the body holds a real return. Its loop skips thunks, which covers the PLT stubs. An external function has no body with a return in it. So neither one is ever cleared. After the script ran on loot, it reported `cleared=4 sites=75 refit=23`. The external `__tls_get_addr` stayed flagged on all 197 call sites. The external `malloc` stayed flagged on all 1,611. I tried a copy of that script that also clears the flag on six externals and their stubs. The six are `malloc`, `__tls_get_addr`, `memcpy`, `memset`, `strlen` and `__errno_location`. The copy then re-disassembles the call sites the same way. After that, the function came back with 59 instructions. The decompile has 74 lines. It now shows the real calls and `PyErr_SetRaisedException`. That copy re-disassembled 1,812 call sites and refit 994 functions in loot. I did not check whether those refits are right (unverified). I tried it on one file. <details> <summary>Commands</summary> ```sh export GHIDRA_INSTALL_DIR=/path/to/ghidra_12.1.4_PUBLIC JAVA_HOME=/path/to/jdk-21 export REA_GHIDRA_STARTUP_TIMEOUT_MS=600000 # REA node scripts/rea.mjs decompile /abs/loot.cpython-313-x86_64-linux-gnu.so PyInit_loot --provider ghidra --format json # Ghidra alone, with a script that prints the facts $GHIDRA_INSTALL_DIR/support/analyzeHeadless /tmp/proj rea-project \ -import /abs/loot.cpython-313-x86_64-linux-gnu.so -readOnly -deleteProject \ -scriptPath /abs/scripts -postScript P4Dump2.java ghidra_default_analysis_only PyInit_ masterlist ``` The script `P4Dump2.java` takes a label, a function name prefix and a list of search terms. It lists the instructions of the matching function. It also prints the no-return flag of each call target and the decompiler text. The script `P4Nr.java` counts call sites per no-return external. I can share both scripts if that helps. The fix idea from the last section is this diff against the REA script: ```diff --- bridge/ghidra/ReaGhidraNoReturnFix.java (4999e4be2) +++ P4NoReturnExt.java (class renamed; run after the original script) @@ -16,7 +16,7 @@ * call as a terminator and the decompiler truncated them. Clear the flag, then re-disassemble only * the call sites of the functions that were cleared and refit their owning functions in place. */ -public final class ReaGhidraNoReturnFix extends GhidraScript { +public final class P4NoReturnExt extends GhidraScript { @Override public void run() throws Exception { // Windows P0 does not admit analysis-database mutation. @@ -24,6 +24,21 @@ FunctionManager fm = currentProgram.getFunctionManager(); Listing li = currentProgram.getListing(); Set<Address> cleared = new HashSet<>(); + Set<String> names = Set.of("__tls_get_addr", "malloc", "memcpy", "memset", "strlen", "__errno_location"); + for (Function e : fm.getExternalFunctions()) { + if (names.contains(e.getName()) && e.hasNoReturn()) { + e.setNoReturn(false); + println("P4X cleared external " + e.getName()); + } + } + for (Function t : fm.getFunctions(true)) { + if (!t.isThunk()) continue; + Function target = t.getThunkedFunction(true); + if (target != null && names.contains(target.getName())) { + if (t.hasNoReturn()) t.setNoReturn(false); + cleared.add(t.getEntryPoint()); + } + } for (Function f : fm.getFunctions(true)) { monitor.checkCancelled(); if (!f.hasNoReturn() || f.isThunk()) continue; ``` </details> <details> <summary>Test setup</summary> - Box: Nobara 44 (Fedora 44 base), Linux 7.2.9 - CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads - RAM: 32 GB DDR4-3600 - Node: 24.18.0 - Main: 4999e4be2 (#1651) - Ghidra: 12.1.4 (Apache-2.0), with JDK 21.0.12.1 - Target: [Amethyst Mod Manager](https://github.com/ChrisDKN/Amethyst-Mod-Manager) 2.5.3 ([GPL-3.0-or-later](https://github.com/ChrisDKN/Amethyst-Mod-Manager/blob/main/LICENSE)) - Target: [libloot](https://github.com/loot/libloot) 0.29.6 ([GPL-3.0-or-later](https://github.com/loot/libloot/blob/master/LICENSE)) </details> Related, but not the same, is #1244. It asks for a warning when a guessed signature clashes with the code. 🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊
bsakaguchi 2026-10-15 18:26:10 +00:00
Author
Owner

@CassieFox23 commented on GitHub (Oct 11, 2026):

TL;DR: I ran main 1e51c732b on the Linux file that the close note called unverified. That is from before #1666 landed. The function still stopped after 8 instructions. But every view named the call and said the flag is only Ghidra's. That matches what 69a8b3a95 set out to do. The #1666 fix then brought back the full body on this file.

What I ran

I built main at 1e51c732b. I opened loot.cpython-313-x86_64-linux-gnu.so with open_binary and provider_id set to ghidra, in one MCP session. The file is the Python module of libloot 0.29.6 (GPL-3.0-or-later). It ships in Amethyst Mod Manager (GPL-3.0-or-later). I then called four tools for PyInit_loot.

Tool Result on 1e51c732b Limitation names __tls_get_addr and 0x4e1046
procedure_pseudo_code 9 lines, ends in __tls_get_addr(&PTR_0068ee28); yes
procedure_assembly 8 instructions, last is 0x4e1046: CALL 0x00629080 yes
procedure_info body 0x4e1020 to 0x4e104a, 43 bytes, 2 basic blocks yes
analyze_function same body and same pseudocode yes

The line is the same in all four results:

Ghidra Listing reports a terminal call at 0x4e1046 to 0x629080 <EXTERNAL>::__tls_get_addr (external=false, thunk=true, hasNoReturn=true); fallthrough is excluded by Ghidra's flow model. This records Ghidra's FunctionManager flag, not an independent verification that the callee cannot return.

The body did not change, as expected: 69a8b3a95 does not clear flags or recover code. In objdump -d the function runs from file offset 0x3e1020 to the ud2 at 0x3e1172. That range holds 75 instructions. The issue body says 78, which was my mistake. I counted the lines objdump prints, and three long instructions wrap onto a second line.

The other file, amethyst_filegraph.abi3.so, was already whole. PyInit_amethyst_filegraph on 1e51c732b has 31 instructions and 157 bytes. Its decompile is 53 lines. It has no terminal-call line.

#1666 landed on main as 49e6f00ed while I tested. Its head takes PyInit_loot from 8 instructions to 59 on this file. The details are in my comment on #1666.

Not run

  • Windows.
  • Hopper and IDA providers.
  • The released 6.3.0 package. I built main from source.
  • The limitation line for the other five flagged imports (malloc, memcpy, memset, strlen, __errno_location). I checked only __tls_get_addr at 0x4e1046.
  • The full test suite.

Thanks, morluto, for the fix in 69a8b3a95.

Test setup
  • Box: Nobara 44 (Fedora 44 base), Linux 7.2.9
  • CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads
  • RAM: 32 GB DDR4-3600
  • Node: 24.18.0
  • Ghidra: 12.1.4 (Apache-2.0) with JDK 21.0.12.1
  • Main: 1e51c732b
  • Files, by SHA-256:
7777474017b65531258cc1678f2c1ee45e61f4b76de4890c02c7436cbbbe60a8  loot.cpython-313-x86_64-linux-gnu.so
288f8035cfd75c0262be2eb4735809b5a65797eb99642fbc56bf39222c99d919  amethyst_filegraph.abi3.so

🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊

<!-- gh-comment-id:6104566048 --> @CassieFox23 commented on GitHub (Oct 11, 2026): **TL;DR:** I ran main 1e51c732b on the Linux file that the close note called unverified. That is from before #1666 landed. The function still stopped after 8 instructions. But every view named the call and said the flag is only Ghidra's. That matches what 69a8b3a95 set out to do. The #1666 fix then brought back the full body on this file. ### What I ran I built main at 1e51c732b. I opened `loot.cpython-313-x86_64-linux-gnu.so` with `open_binary` and `provider_id` set to `ghidra`, in one MCP session. The file is the Python module of libloot 0.29.6 ([GPL-3.0-or-later](https://github.com/loot/libloot/blob/master/LICENSE)). It ships in Amethyst Mod Manager ([GPL-3.0-or-later](https://github.com/ChrisDKN/Amethyst-Mod-Manager/blob/main/LICENSE)). I then called four tools for `PyInit_loot`. | Tool | Result on 1e51c732b | Limitation names `__tls_get_addr` and 0x4e1046 | |---|---|---| | `procedure_pseudo_code` | 9 lines, ends in `__tls_get_addr(&PTR_0068ee28);` | yes | | `procedure_assembly` | 8 instructions, last is `0x4e1046: CALL 0x00629080` | yes | | `procedure_info` | body 0x4e1020 to 0x4e104a, 43 bytes, 2 basic blocks | yes | | `analyze_function` | same body and same pseudocode | yes | The line is the same in all four results: ```text Ghidra Listing reports a terminal call at 0x4e1046 to 0x629080 <EXTERNAL>::__tls_get_addr (external=false, thunk=true, hasNoReturn=true); fallthrough is excluded by Ghidra's flow model. This records Ghidra's FunctionManager flag, not an independent verification that the callee cannot return. ``` The body did not change, as expected: 69a8b3a95 does not clear flags or recover code. In `objdump -d` the function runs from file offset 0x3e1020 to the `ud2` at 0x3e1172. That range holds 75 instructions. The issue body says 78, which was my mistake. I counted the lines `objdump` prints, and three long instructions wrap onto a second line. The other file, `amethyst_filegraph.abi3.so`, was already whole. `PyInit_amethyst_filegraph` on 1e51c732b has 31 instructions and 157 bytes. Its decompile is 53 lines. It has no terminal-call line. #1666 landed on main as 49e6f00ed while I tested. Its head takes `PyInit_loot` from 8 instructions to 59 on this file. The details are in [my comment on #1666](https://github.com/morluto/rea/pull/1666#issuecomment-6104565085). ### Not run - Windows. - Hopper and IDA providers. - The released 6.3.0 package. I built main from source. - The limitation line for the other five flagged imports (`malloc`, `memcpy`, `memset`, `strlen`, `__errno_location`). I checked only `__tls_get_addr` at 0x4e1046. - The full test suite. Thanks, morluto, for the fix in 69a8b3a95. <details> <summary>Test setup</summary> - Box: Nobara 44 (Fedora 44 base), Linux 7.2.9 - CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads - RAM: 32 GB DDR4-3600 - Node: 24.18.0 - Ghidra: 12.1.4 ([Apache-2.0](https://github.com/NationalSecurityAgency/ghidra/blob/master/LICENSE)) with JDK 21.0.12.1 - Main: 1e51c732b - Files, by SHA-256: ```text 7777474017b65531258cc1678f2c1ee45e61f4b76de4890c02c7436cbbbe60a8 loot.cpython-313-x86_64-linux-gnu.so 288f8035cfd75c0262be2eb4735809b5a65797eb99642fbc56bf39222c99d919 amethyst_filegraph.abi3.so ``` </details> 🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊
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#560
No description provided.