Skip to content

Where a function starts

Level: 301 · for anyone who has opened a stripped binary and wondered how Ghidra knew where the functions were

One line: A compiler begins nearly every function the same way — clang starts all seven functions on this page with the four bytes 55 48 89 e5, at -O0 and at -O2 — so a program with its names stripped still says where its functions begin, and Ghidra finds them by searching for those bytes; the Function Bit Patterns Explorer is how such bytes are discovered for a compiler Ghidra has no pattern file for, and this page drives it from gathering to an exported pattern file, then shows the case it exists for: GCC at -O2, where the functions share nothing and the shipped patterns miss five of seven.

The program

Six functions and a main that calls five of them — demo/prologues.c:

/* Six functions of different shapes and a main that calls five of them -- the
   program this page builds, strips, and hands to Ghidra to see what a function
   start looks like from the outside. */
#include <stdio.h>
#include <string.h>

/* A leaf: no calls, one parameter, nothing worth a stack frame. */
int twice(int x)
{
    return 2 * x;
}

/* Branches. */
int clamp(int x, int lo, int hi)
{
    if (x < lo) {
        return lo;
    }
    if (x > hi) {
        return hi;
    }
    return x;
}

/* A loop over an array. */
long sum(const int *a, int n)
{
    long total = 0;
    for (int i = 0; i < n; i++) {
        total += a[i];
    }
    return total;
}

/* A stack buffer and two library calls. */
int greet_len(const char *who)
{
    char buf[64];
    snprintf(buf, sizeof buf, "hello, %s", who);
    return (int)strlen(buf);
}

/* One call, in tail position. */
void say(const char *word)
{
    printf("%s\n", word);
}

/* Nothing calls this. The compiler keeps it because it has external linkage,
   the linker keeps it because nothing told it not to -- and after strip, its
   bytes are all that is left of it. */
int orphan(int x)
{
    printf("nobody calls this: %d\n", x);
    return x + 1;
}

int main(void)
{
    int a[] = { 1, 2, 3, 4 };
    printf("twice(21) = %d\n", twice(21));
    printf("clamp(15, 0, 10) = %d\n", clamp(15, 0, 10));
    printf("sum({1, 2, 3, 4}) = %ld\n", sum(a, 4));
    printf("greet_len(\"world\") = %d\n", greet_len("world"));
    say("prologue");
    return 0;
}
Function What it is there to show
twice a leaf, fourteen bytes long from push to ret — shorter than the window this page gathers, which matters below
clamp branches
sum a loop
greet_len a stack buffer, so a frame to set up, and two library calls
say one call in tail position, which GCC at -O2 turns into a jump
orphan a function nothing calls: it stays in the file, and after strip its bytes are the only trace of it

Build it, run it, and ask the file about orphan before and after strip — by name while there is one, and by the one string only it uses when there is not:

Verified output of function_start_patterns_sh.sh — regenerated by tools/run_examples.py, never hand-typed.

$ cc -std=c17 -Wall -Wextra -O0 -o prologues prologues.c
$ ./prologues
twice(21) = 42
clamp(15, 0, 10) = 10
sum({1, 2, 3, 4}) = 10
greet_len("world") = 12
prologue
$ names prologues
clamp greet_len main orphan say sum twice 
$ grep -c -a 'nobody calls this' prologues
1
$ strip prologues
$ names prologues

$ grep -c -a 'nobody calls this' prologues
1
$ ./prologues
twice(21) = 42
clamp(15, 0, 10) = 10
sum({1, 2, 3, 4}) = 10
greet_len("world") = 12
prologue

The names go, the string stays, the program runs the same. The previous lesson stopped there: after strip, Ghidra called every function FUN_ and its address and decompiled them all the same as before. It did not say how Ghidra knew where each one started once no symbol said so. orphan is the function to ask that about, because nothing calls it — so no call instruction leads there either, and the only thing left to go on is what its bytes look like.

A pattern is a value and a mask

What they look like is the compiler's habit. Apple's clang, at -O0 and at -O2, opens every function on this page with push rbp — the byte 55 — and mov rbp, rsp — the bytes 48 89 e5 — before anything the function itself does. Ghidra ships with files of such habits, per processor and compiler, in the data/patterns folder of the processor module; the x86-64 GCC file ↗ has this line, which is the habit above plus the sub rsp, N that follows it when a function needs stack space:

<data>0x55 0x48 0x89 0xe5 0x48 100000.1 0xec  .....000</data>             <!-- PUSH RBP; MOV RBP, RSP; SUB RSP, C -->

A byte with no dots is fixed. A group of eight bit characters is a byte with dits in it — . for a bit that may be either — so 100000.1 is 0x81 or 0x83, the two encodings of sub with a 32-bit or an 8-bit constant, and .....000 is any constant that is a multiple of eight. That is the whole representation: a pattern is a value and a mask, and Ghidra's DittedBitSequence keeps exactly those two byte arrays. Two operations on it are all the explorer's arithmetic — merge some sequences into a pattern, keeping a bit only where every sequence agrees, and match a pattern against bytes — and both fit in one C file. The four sequences it merges are the first eight bytes of the four functions here that set up a frame, as Ghidra gathered them further down:

Verified output of function_start_patterns_c.c — regenerated by tools/run_examples.py, never hand-typed.

merged:  0x55 0x48 0x89 0xe5 0x48 0x83 0xec 0...0000
61 of 64 bits fixed
match     sub rsp, 0x20 -- a frame none of the four had
no match  sub rsp, 0x28 -- not a multiple of 16
no match  twice, which sets up no frame at all
no match  the first eight bytes of "nobody calls this"

The merge fixed the low four bits of the frame size to zero because all four samples were multiples of sixteen, and it fixed the 0x83 because none of them needed a 32-bit constant. Ghidra's hand-written line above allows either encoding and any multiple of eight: it was written from more samples, and loosened on purpose. Four functions from one program are a small sample, and the steps below — filtering, mining, evaluating — are how the explorer lets you see how far to loosen.

What the explorer gathers

In the Code Browser, Window → Function Bit Patterns Explorer opens the window, and its Gather Data from Current Program button (or Tools → Explore Function Bit Patterns) asks for six window sizes — how many bytes and how many instructions to record at the start of each function, just before it, and up to each of its returns — and, optionally, for context registers to record, then fills the tabs. demo/patterns.sh does the same without the window: it builds the program, hands it to the headless analyzer, and runs ExploreFunctionStarts.java, which gathers with the explorer's defaults — 16 bytes and 4 instructions at the start, 12 and 3 before, 12 and 3 at each return — writes the XML the Read XML Files button reads, and then takes, in order, the steps a reader takes in the window:

#!/usr/bin/env bash
# Build prologues.c, hand the program to Ghidra's headless analyzer, and print
# what the Function Bit Patterns Explorer shows for it -- gathered and worked
# through by ExploreFunctionStarts.java, without the window. With KEEP set to a
# directory, also keeps the XML the explorer's "Read XML Files" button reads and
# the pattern file its Export button writes.
#
#   demo/patterns.sh            the clang -O0 build
#   demo/patterns.sh -O2        any other optimisation level
#   demo/patterns.sh <binary>   an executable built elsewhere -- by GCC in Docker, say
#
# Needs a Ghidra install. Homebrew's is found through `brew --prefix ghidra`;
# any other one through GHIDRA_INSTALL_DIR, the folder holding support/.
set -eu
here=$(cd "$(dirname "$0")" && pwd)
ghidra=${GHIDRA_INSTALL_DIR:-$(brew --prefix ghidra)/libexec}
work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT

if [ $# -ge 1 ] && [ -f "$1" ]; then
    binary=$1
else
    level=${1:--O0}
    binary=$work/prologues$level
    cc -std=c17 -Wall -Wextra "$level" -o "$binary" "$here/prologues.c"
fi

mkdir -p "$work/xml"
"$ghidra/support/analyzeHeadless" "$work" prologues -import "$binary" \
    -scriptPath "$here" \
    -postScript ExploreFunctionStarts.java "$work/explorer.txt" "$work/xml" \
    -deleteProject > "$work/ghidra.log" 2>&1 || { cat "$work/ghidra.log"; exit 1; }

cat "$work/explorer.txt"
if [ -n "${KEEP:-}" ]; then
    mkdir -p "$KEEP"
    cp "$work"/xml/* "$KEEP"/
fi

Everything Ghidra printed on this page was produced on 2026-09-13 by Ghidra 12.1.3 from Homebrew on OpenJDK 25, on an x86-64 Mac running macOS 26 with Apple clang 21. CI does not run it — the runners have no Ghidra — so each block is a Real output fence that says so, and the program the fences describe is the one the verified block above built and ran. First, what was gathered, one function at a time:

Real output — Ghidra 12.1.3 headless on the clang -O0 build, x86-64 macOS; demo/patterns.sh, the gathering
== prologues-O0: 7 functions gathered  (first 16 bytes / 4 instructions, pre 12 / 3, return 12 / 3)
_twice @ 1000004c0
  first   55 48 89 e5 89 7d fc 8b 45 fc d1 e0 5d c3       PUSH:1 MOV:3 MOV:3 MOV:3
  pre     00 00 00 00 00 00 00 00 00 00 00 00             (none)
  return  89 e5 89 7d fc 8b 45 fc d1 e0 5d c3             SHL:2 POP:1 RET:1
_clamp @ 1000004d0
  first   55 48 89 e5 89 7d f8 89 75 f4 89 55 f0 8b 45 f8 PUSH:1 MOV:3 MOV:3 MOV:3
  pre     89 7d fc 8b 45 fc d1 e0 5d c3 66 90             (none)
  return  06 8b 45 f8 89 45 fc 8b 45 fc 5d c3             MOV:3 POP:1 RET:1
_sum @ 100000510
  first   55 48 89 e5 48 89 7d f8 89 75 f4 48 c7 45 e8 00 PUSH:1 MOV:3 MOV:4 MOV:3
  pre     45 fc 5d c3 0f 1f 84 00 00 00 00 00             (none)
  return  01 89 45 e4 eb d9 48 8b 45 e8 5d c3             MOV:4 POP:1 RET:1
_greet_len @ 100000560
  first   55 48 89 e5 48 83 ec 60 48 8b 05 91 0a 00 00 48 PUSH:1 MOV:3 SUB:4 MOV:7
  pre     e8 5d c3 66 0f 1f 84 00 00 00 00 00             (none)
  return  a4 48 83 c4 60 5d c3 e8 4b 01 00 00             POP:1 RET:1 CALL:5
  return  c8 75 09 8b 45 a4 48 83 c4 60 5d c3             ADD:4 POP:1 RET:1
_say @ 1000005d0
  first   55 48 89 e5 48 83 ec 10 48 89 7d f8 48 8b 75 f8 PUSH:1 MOV:3 SUB:4 MOV:4
  pre     01 00 00 66 0f 1f 84 00 00 00 00 00             (none)
  return  00 e8 2a 01 00 00 48 83 c4 10 5d c3             ADD:4 POP:1 RET:1
_orphan @ 100000600
  first   55 48 89 e5 48 83 ec 10 89 7d fc 8b 75 fc 48 8d PUSH:1 MOV:3 SUB:4 MOV:3
  pre     66 66 66 2e 0f 1f 84 00 00 00 00 00             (none)
  return  8b 45 fc 83 c0 01 48 83 c4 10 5d c3             ADD:4 POP:1 RET:1
entry @ 100000630
  first   55 48 89 e5 48 83 ec 30 48 8b 05 c1 09 00 00 48 PUSH:1 MOV:3 SUB:4 MOV:7
  pre     c4 10 5d c3 0f 1f 84 00 00 00 00 00             (none)
  return  39 c8 75 08 31 c0 48 83 c4 30 5d c3             ADD:4 POP:1 RET:1
  return  c0 48 83 c4 30 5d c3 e8 0d 00 00 00             POP:1 RET:1 CALL:5

Every first line opens 55 48 89 e5. twice is the short one: fourteen bytes from its push to its ret, so the sixteen-byte window ran out of function, and PUSH:1 MOV:3 MOV:3 MOV:3 — the labels are mnemonic and length in bytes — is all of it but the last two instructions. main is there as entry, because the Mach-O load command that names the entry point takes precedence over the symbol. The pre lines are the bytes before each function, and on a Mac they are padding: 66 90, 0f 1f 84 00 00 00 00 00, 66 0f 1f 84 00 00 00 00 00 and 66 66 66 2e 0f 1f 84 00 00 00 00 00 are multi-byte forms of nop — two, eight, nine and twelve bytes of it in these windows — put there so that every function starts on a sixteen-byte boundary; the twelve zero bytes before twice are the end of the gap between the last load command and the first byte of code, which the linker fills with zeros. And there are nine return lines for seven functions, because greet_len and main each have a second ending: a call to ___stack_chk_fail, the stack-protector check clang adds to a function with an array on its stack, which never returns — Ghidra ends the flow at such a call, and the explorer counts it as a return, CALL:5 and all.

Real output — the same run; the three instruction trees
== First Instructions Tree: 7 sequences ==
  PUSH:1     7 of 7  100%
    MOV:3      7 of 7  100%
      SUB:4      4 of 7   57%
        MOV:7      2 of 7   29%
        MOV:3      1 of 7   14%
        MOV:4      1 of 7   14%
      MOV:3      2 of 7   29%
        MOV:3      2 of 7   29%
      MOV:4      1 of 7   14%
        MOV:3      1 of 7   14%
== Pre-Instructions Tree: 0 sequences; the root is the instruction just before the start ==
== Return Instructions Tree: 9 sequences; the root is the return itself ==
  RET:1      7 of 9   78%
    POP:1      7 of 9   78%
      ADD:4      4 of 9   44%
      MOV:3      1 of 9   11%
      MOV:4      1 of 9   11%
      SHL:2      1 of 9   11%
  CALL:5     2 of 9   22%
    RET:1      2 of 9   22%
      POP:1      2 of 9   22%

These are the three instruction-sequence tabs. Each is a tree: one node per instruction, labelled with the mnemonic and its length in bytes, so a one-byte push rbp and a two-byte push r12 take different branches; a function's sequence is a path from the root, and paths that agree share nodes. The number the window shows when you hover a node is the share of all sequences that pass through it — SUB:4 under PUSH:1 → MOV:3 is four functions of seven — and the percentage filter prunes every node below a share you name, which turns a large program's tree into its handful of common openings. The pre-instructions tree is empty because the padding before each function was never disassembled: it is bytes, not instructions, and Ghidra has no reason to decode it. The return tree is read from the return backwards — RET:1 at the root, then the pop rbp before it — and the two CALL:5 roots are the two calls that never return.

Selecting, merging, mining

The three byte-sequence tabs hold the raw bytes, and they will not show a table until you give them a length filter: a minimum length, which drops every sequence shorter than it, and a prefix or suffix length to cut the rest to, so that everything in the table is the same size and can be compared bit by bit — a negative number means a suffix, which is what you want for pre-bytes and return bytes, where the interesting end is the end. The script applies three: the first four bytes of every start, the bytes of one tree path, and the last two bytes of every return.

Real output — the same run; the First Bytes and Return Bytes tabs under a length filter, one node analyzed, and three merges
== First Bytes, length filter: at least 4 bytes, keep the first 4: 7 of 7 sequences pass ==
  55 48 89 e5   7 of 7  100%
  Send Selected to Clipboard, the top row only:  0x55 0x48 0x89 0xe5   (32 of 32 bits fixed)

== Analyze Sequences on the node PUSH:1 > MOV:3 > SUB:4: 4 of 7 sequences, 8 bytes each ==
  55 48 89 e5 48 83 ec 10      2    PUSH:1(RBP)  MOV:3(RBP,RSP)  SUB:4(RSP,0x10) 
  55 48 89 e5 48 83 ec 30      1    PUSH:1(RBP)  MOV:3(RBP,RSP)  SUB:4(RSP,0x30) 
  55 48 89 e5 48 83 ec 60      1    PUSH:1(RBP)  MOV:3(RBP,RSP)  SUB:4(RSP,0x60) 
  Merge Selected Rows, all of them:  0x55 0x48 0x89 0xe5 0x48 0x83 0xec 0...0000   (61 of 64 bits fixed)
  On the whole 16-byte window, which 6 of 7 sequences fill:
    0x55 0x48 0x89 0xe5 ..00100. .......1 .11.1.0. .....00. ......0. ........ ......0. ........ ........ ........ ......0. ......0.   (48 of 128 bits fixed)

== Return Bytes, length filter: at least 2 bytes, keep the last 2: 9 sequences ==
  5d c3         7 of 9   78%
  00 00         2 of 9   22%
  Send Selected to Clipboard, the top row only:  0x5d 0xc3   (16 of 16 bits fixed)

The four-byte table has one row: all seven functions, one sequence, and Send Selected to Clipboard sends it to the Pattern Clipboard as it is. Analyze Sequences, on a tree node instead, takes the bytes of just that path — here the four functions that go PUSH:1 → MOV:3 → SUB:4, eight bytes each, with their disassembly — and Merge Selected Rows on those is the merge the C program above computed: 0...0000, three dits where the frame sizes differed. The same merge over the whole sixteen-byte window is where the length filter bites — 6 of 7 sequences fill it, because twice was fourteen bytes and was dropped — and it fixes 48 of 128 bits, the four-byte prologue and a scattering of bits after it that six functions happen to share. The return table is nine rows collapsed to two: 5d c3, pop rbp; ret, and the 00 00 that ends the two calls to ___stack_chk_fail, and the top row alone goes to the clipboard as the PRE pattern.

Real output — the same run; Mine Sequential Patterns on the six full first-bytes sequences
== Mine Sequential Patterns on the 6 full 16-byte first sequences: support >= 50%, >= 16 fixed bits, as bits ==
  30 closed patterns; the first 8, most common first:
  0x55 0x48 0x89 0xe5 ..00100. .......1 .11.1.0. .....00. ......0. ........ ......0. ........ ........ ........ ......0. ......0.  48 fixed bits   6 of 6  100%
  0x55 0x48 0x89 0xe5 0x48 1000.0.1 .11.110. .....000 ..00100. .......1 .....10. .....0.. ........ ........ ......0. ......0.  65 fixed bits   5 of 6   83%
  0x55 0x48 0x89 0xe5 ..00100. .......1 .11.1.0. .....00. ......0. ........ ......0. ......0. ........ ..00.... ......0. .....000  53 fixed bits   5 of 6   83%
  0x55 0x48 0x89 0xe5 ..00100. .......1 111.1.00 .....00. ......0. ........ ......0. ........ ........ ........ 0.....0. ....1.0.  52 fixed bits   5 of 6   83%
  0x55 0x48 0x89 0xe5 ..00100. .......1 .11.1.0. .....00. ......0. ........ ......0. ..0..... ........ ........ ...0..0. ......0.  50 fixed bits   5 of 6   83%
  0x55 0x48 0x89 0xe5 0x48 0x83 0xec 0...0000 ..00100. ....1..1 .....10. 1....0.. 0....... ........ 0.....0. ....1.0.  77 fixed bits   4 of 6   67%
  0x55 0x48 0x89 0xe5 0x48 1000.0.1 .11.110. .....000 ..00100. .......1 .....10. .....00. ..00.... ..00.... ......0. .....000  72 fixed bits   4 of 6   67%
  0x55 0x48 0x89 0xe5 0x48 1000.0.1 .11.110. .....000 ..00100. .......1 .....10. ..0..0.. ........ ......0. ...0.000 ..00..0.  72 fixed bits   4 of 6   67%

Mine Sequential Patterns is the third way to a pattern, and the one for a large table. It asks for a minimum support — the share of sequences a pattern must occur in — a minimum number of fixed bits, and whether to work on bits or on hex digits, which is faster and finds less. It returns closed patterns: a pattern is closed when no pattern that fixes more bits occurs in exactly the same sequences, so the list never contains a pattern and a strictly-less-specific copy of it with the same support, and the combinatorial explosion of sub-patterns is cut off — the algorithm is BIDE, in ClosedSequenceMiner. The first pattern it found, 48 bits in all six sequences, is the merge above. The rest trade support for bits: 65 fixed bits in five of six, 77 in four — the frame-setting four, with their 0x48 0x83 0xec — and so on down to three of six, thirty patterns in all from six sequences. Any row can go to the clipboard.

Export and evaluate

The clipboard holds patterns of two types. A pattern from first bytes or first instructions is POST: it begins at a function start. A pattern from pre-bytes or pre-instructions is PRE: it ends just before one — and so is a pattern from return bytes, on the reasoning that a return is normally the last thing before the next function. Export writes the selected patterns in the format the Function Start Analyzer reads, asking for two numbers on the way, and the script exports its two:

Real output — the same run; Export, then Evaluate against the program
== Export Selected Patterns, total bits 32, post bits 16: prologues-O0_patterns.xml ==
<patternlist>
  <patternpairs totalbits="32" postbits="16">
    <prepatterns>
        <data>0x5d 0xc3</data>
    </prepatterns>
    <postpatterns>
       <data>0x55 0x48 0x89 0xe5</data>
       <funcstart/>
    </postpatterns>
  </patternpairs>
</patternlist>
== Evaluate: the POST pattern 0x55 0x48 0x89 0xe5, everywhere in the program ==
  1000004c0  __text     TRUE_POSITIVE            _twice
  1000004d0  __text     TRUE_POSITIVE            _clamp
  100000510  __text     TRUE_POSITIVE            _sum
  100000560  __text     TRUE_POSITIVE            _greet_len
  1000005d0  __text     TRUE_POSITIVE            _say
  100000600  __text     TRUE_POSITIVE            _orphan
  100000630  __text     TRUE_POSITIVE            entry
  totals: {TRUE_POSITIVE=7}
== Evaluate: PRE followed immediately by POST, 0x5d 0xc3 0x55 0x48 0x89 0xe5 ==
  totals: {}

That file is the shape of the one at the top of the page. When the analyzer loads it, it pairs every PRE pattern with every POST pattern and keeps a pair only if the two together fix at least totalbits bits, at least postbits of them on the POST side — PatternPairSet — which is what stops a two-byte PRE such as 5d c3 from marking a function start after every pop rbp; ret in the program, and <funcstart/> is the action: where a pair matches, the byte after the PRE part is a function.

Evaluate Selected Patterns is the test before the export: it searches the current program for the selected patterns — PRE and POST concatenated, if both are selected — and labels every hit. The names are the explorer's own. TRUE_POSITIVE is a hit where a function already starts; FP_DATA is one inside defined data; POSSIBLE_START_UNDEFINED is in bytes nothing has decoded yet, which in a stripped program is where the missing functions are; FP_MISALIGNED lands inside an instruction; FP_WRONG_FLOW is at an instruction that code already flows into; and POSSIBLE_START_CODE is code at the start of a block that only jumps reach. Alone, the four-byte POST pattern hit seven addresses, all seven of them function starts, and nothing else in the file. Paired with 5d c3 it hit nothing at all — because on a Mac there is padding between one function's ret and the next function's push, and a pair has to be contiguous. That is why the shipped pre-pattern lists are mostly forms of nop: 0x90 0x90, 0x6690, 0x0f1f00, up to the nine-byte 0x660f1f840000000000, each of them something that sits between two functions. GCC at -O0 does not pad, and the same two patterns on that build show the difference:

Real output — Ghidra 12.1.3 on the Mac, on the ELF that GCC 14.4.0 built from the same source at -O0 in the gcc:14 Docker image, x86-64; the same script's Evaluate step
== Evaluate: the POST pattern 0x55 0x48 0x89 0xe5, everywhere in the program ==
  0040112d  .text      FP_WRONG_FLOW            
  00401156  .text      TRUE_POSITIVE            twice
  00401164  .text      TRUE_POSITIVE            clamp
  00401190  .text      TRUE_POSITIVE            sum
  004011da  .text      TRUE_POSITIVE            greet_len
  00401216  .text      TRUE_POSITIVE            say
  00401231  .text      TRUE_POSITIVE            orphan
  00401258  .text      TRUE_POSITIVE            main
  totals: {FP_WRONG_FLOW=1, TRUE_POSITIVE=7}
== Evaluate: PRE followed immediately by POST, 0x5d 0xc3 0x55 0x48 0x89 0xe5 ==
  00401164  .text      TRUE_POSITIVE            clamp
  00401190  .text      TRUE_POSITIVE            sum
  004011da  .text      TRUE_POSITIVE            greet_len
  totals: {TRUE_POSITIVE=3}

The POST pattern alone found the seven functions and one more hit, FP_WRONG_FLOW, inside __do_global_dtors_aux — a push rbp after a branch in the middle of a function the C runtime contributed, which is why a four-byte POST pattern is not a function-start rule on its own, and why the shipped file pairs it with a PRE. The pair 5d c3 55 48 89 e5 found three: clamp, sum and greet_len, the three functions whose predecessor ends in exactly pop rbp; ret with nothing between. The other four have leave; ret before them — or, for twice, the jmp that ends the runtime's frame_dummy — which is what the 0xc9 0xc3 and 0xeb.. rows among the shipped file's PRE patterns are for.

Two more buttons complete the clipboard. Create Functions applies the selected patterns to the current program itself, making a function wherever they match and none exists yet, which is the single-program use of the tool. Import reads a pattern file back in, and the help warns to import only files this plugin wrote, because the shipped files use attributes it does not know. An exported file becomes part of auto-analysis by being placed in the processor's data/patterns folder and named in its patternconstraints.xml, which maps a language and compiler to the files that apply.

What finds a function in a stripped file

So which of this found orphan? demo/found.sh builds the program, strips it, and has Ghidra analyze it with the analyzers as shipped and again with the four pattern-search analyzers switched off by NoPatternSearch.java — then does it again for a build with one linker flag, and for four ELF builds that demo/build_in_docker.sh made with GCC. FunctionsFound.java reports, for each run, how many functions Ghidra found in the program's own code and which of the seven it did not, checked by address against the build that still had a symbol table:

Real output — demo/found.sh on the Mac, with the four GCC builds from demo/build_in_docker.sh; twelve headless runs
clang -O0, stripped                           7 in __text all 7 named functions found
  the same, pattern search off                7 in __text all 7 named functions found
clang -O0, stripped, no LC_FUNCTION_STARTS    7 in __text all 7 named functions found
  the same, pattern search off                6 in __text missing: orphan
prologues_gcc0, stripped                     12 in .text  all 7 named functions found
  the same, pattern search off               12 in .text  all 7 named functions found
prologues_gcc0_nounwind, stripped            12 in .text  all 7 named functions found
  the same, pattern search off               10 in .text  missing: main orphan
prologues_gcc2, stripped                     11 in .text  all 7 named functions found
  the same, pattern search off               11 in .text  all 7 named functions found
prologues_gcc2_nounwind, stripped             7 in .text  missing: clamp orphan say sum twice
  the same, pattern search off                6 in .text  missing: clamp main orphan say sum twice

Read it in pairs. The stripped Mac binary gave up all seven functions with the pattern search switched off, so patterns are not what found them: the file has a load command, LC_FUNCTION_STARTS, that lists every function start so that a debugger or a crash report can find them, strip leaves it in, and Ghidra's Mach-O Function Starts analyzer reads it. Link with -Wl,-no_function_starts and the table is gone; now Function Start Search, the analyzer that reads the pattern files, finds orphan by the first pair in the file, and with the pattern search off it is the one function missing. The ELF that GCC built has the same thing under another name: .eh_frame, the unwind table that GCC emits for every function by default so that a stack can be walked through it, which strip keeps because the program may need it at run time, and which Ghidra's GCC Exception Handlers analyzer reads. Build without it, -fno-asynchronous-unwind-tables, and at -O0 the story is the Mac's: patterns find all seven, and without patterns orphan is missing — and so is main, which nothing calls either: _start hands its address to the C library as a number in a register, and a number is not a call.

At -O2, without the unwind table, the shipped patterns find main — it opens with sub rsp, 8, which the file has as a pattern on its own, on condition that the bytes before it are already defined and that ten valid instructions follow — and greet_len, because main calls it; twice, clamp, sum, say and orphan are gone. This is the case the explorer exists for, and it is what the explorer sees on that build while the symbols are still there to say where every function is:

Abridged — real output, the same script on the GCC -O2 build with its symbols; the first-instructions tree and the First Bytes tab
== First Instructions Tree: 14 sequences ==
  SUB:4      3 of 14   21%
    ADD:4      1 of 14    7%
      RET:1      1 of 14    7%
    MOV:5      1 of 14    7%
      MOV:5      1 of 14    7%
        XOR:2      1 of 14    7%
    MOV:7      1 of 14    7%
      TEST:3     1 of 14    7%
        JZ:2       1 of 14    7%
  MOV:5      2 of 14   14%
    CMP:6      1 of 14    7%
      JZ:2       1 of 14    7%
        MOV:5      1 of 14    7%
    SUB:7      1 of 14    7%
      MOV:3      1 of 14    7%
        SHR:4      1 of 14    7%
  PUSH:1     2 of 14   14%
    MOV:2      1 of 14    7%
      MOV:2      1 of 14    7%
        XOR:2      1 of 14    7%
    MOV:3      1 of 14    7%
      MOV:5      1 of 14    7%
        MOV:5      1 of 14    7%
  CMP:2      1 of 14    7%
    MOV:2      1 of 14    7%
      CMOVLE:3   1 of 14    7%
        CMP:2      1 of 14    7%
  ENDBR64:4  1 of 14    7%
    CMP:7      1 of 14    7%
      JNZ:2      1 of 14    7%
        PUSH:1     1 of 14    7%
  LEA:3      1 of 14    7%
    RET:1      1 of 14    7%
  PUSH:6     1 of 14    7%
    JMP:6      1 of 14    7%
  RET:1      1 of 14    7%
  TEST:2     1 of 14    7%
    JLE:2      1 of 14    7%
      MOVSXD:3   1 of 14    7%
        XOR:2      1 of 14    7%
  XOR:2      1 of 14    7%
    MOV:3      1 of 14    7%
      POP:1      1 of 14    7%
        MOV:3      1 of 14    7%
...
== First Bytes, length filter: at least 4 bytes, keep the first 4: 13 of 14 sequences pass ==
  48 83 ec 08   3 of 13   23%
  85 f6 7e 1c   1 of 13    8%
  f3 0f 1e fa   1 of 13    8%
  39 d7 89 f0   1 of 13    8%
  53 48 89 f9   1 of 13    8%
  53 89 fe 89   1 of 13    8%
  31 ed 49 89   1 of 13    8%
  be 30 40 40   1 of 13    8%
  ff 35 ca 2f   1 of 13    8%
  8d 04 3f c3   1 of 13    8%
  b8 30 40 40   1 of 13    8%
  Send Selected to Clipboard, the top row only:  0x48 0x83 0xec 0x08   (32 of 32 bits fixed)

Fourteen functions, and no two of the seven from the source begin alike: 8d 04 3f c3 is all of twicelea eax, [rdi + rdi]; ret39 d7 89 f0 opens clamp with a compare, 85 f6 7e 1c opens sum with a test, 53 89 fe 89 opens orphan with push rbx, and say is not in the list at all, because GCC made it a bare jmp to puts and Ghidra files that as a thunk. The tree is flat, the merge of the table is all dits, and no POST pattern can be written for a compiler that omits the frame pointer and starts each function with its first useful instruction. What these functions do share is what comes before them, in the pre lines of the full output: 66 2e 0f 1f 84 00 00 00 00 00 and 66 66 2e 0f 1f 84 00 00 00 00 00, the ten- and eleven-byte nop forms that pad each one to its boundary — and a PRE pattern with no POST is a different kind of rule, which the shipped file writes only for endbr64, with <codeboundary/> in place of <funcstart/>, and which its single patterns approach with a validcode count that demands that many decodable instructions after the match. Writing that rule for these two nop forms is the exercise this page leaves open; the tool that shows you the two forms, and lets you check a candidate against the file before committing it, is the one this page has been driving.

The window

The script's output maps onto the window's tabs one to one. First Instructions Tree, Pre-Instructions Tree and Return Instructions Tree are the three trees, with the percentage filter and a context-register filter each. First Bytes, Pre-Bytes and Return Bytes are the three tables, each behind its own length filter — filters are not shared between tabs — with Analyze Sequences on a selection of rows leading to the merge and mining actions, and Analyze Sequences on a tree node leading to the merge on that path's bytes — mining is offered only for sequences that came from a byte tab. Function Start Alignment is a histogram of function addresses modulo a number you choose: the Mac addresses above all end in 0, so every one of the seven lands in row zero for a modulus of sixteen, and the GCC -O0 addresses — 401156, 401164, 401190 — do not, which tells you whether a search can skip unaligned addresses. Context Register Information lists the values of the processor-context registers you named when gathering — on ARM, TMode, so that Thumb and ARM prologues are kept apart — and a filter on those registers can be applied in any other tab. Pattern Clipboard is the last tab, with the buttons above, an Alignment column you can edit, and a context-register column filled from whatever register filter was in force when a pattern was sent.

The other data source is a directory of XML files, one per program, made by running Ghidra's DumpFunctionPatternInfoScript.java over a corpus — its .properties file holds the six numbers, and the defaults there are the ones this page used — and Read XML Files loads them all, which is how a pattern file for a compiler is made from many of its programs rather than one. The script here writes the same file for the program it gathered:

Abridged — real output, the XML ExploreFunctionStarts.java wrote for the clang -O0 build; the first function of seven
<?xml version="1.0" encoding="UTF-8"?>
<FileBitPatternInfo ghidraURL="/prologues-O0" languageID="x86:LE:64:default" numFirstBytes="16" numFirstInstructions="4" numPreBytes="12" numPreInstructions="3" numReturnBytes="12" numReturnInstructions="3">
  <funcBitPatternInfoList>
    <FunctionBitPatternInfo preBytes="000000000000000000000000" firstBytes="554889e5897dfc8b45fcd1e05dc3" address="1000004c0">
      <returnBytesList>
        <returnBytes value="89e5897dfc8b45fcd1e05dc3" />
      </returnBytesList>
      <firstInst>
        <instructions>
          <instruction value="PUSH" />
          <instruction value="MOV" />
          <instruction value="MOV" />
          <instruction value="MOV" />
        </instructions>
        <sizes>
          <size value="1" />
          <size value="3" />
          <size value="3" />
          <size value="3" />
        </sizes>
        <commaSeparatedOperands>
          <operands value="RBP" />
          <operands value="RBP,RSP" />
          <operands value="dword ptr [RBP + -0x4],EDI" />
          <operands value="EAX,dword ptr [RBP + -0x4]" />
        </commaSeparatedOperands>
      </firstInst>
      <preInst>
        <instructions>
          <instruction />
          <instruction />
          <instruction />
        </instructions>
        <sizes>
          <size />
          <size />
          <size />
        </sizes>
        <commaSeparatedOperands>
          <operands />
          <operands />
          <operands />
        </commaSeparatedOperands>
      </preInst>
      <returnInstList>
        <InstructionSequence>
          <instructions>
            <instruction value="RET" />
            <instruction value="POP" />
            <instruction value="SHL" />
          </instructions>
          <sizes>
            <size value="1" />
            <size value="1" />
            <size value="2" />
          </sizes>
          <commaSeparatedOperands>
            <operands value="" />
            <operands value="RBP" />
            <operands value="EAX,0x1" />
          </commaSeparatedOperands>
        </InstructionSequence>
      </returnInstList>
      <contextRegistersList />
    </FunctionBitPatternInfo>
    ...

If you are coming from another language

Rust. The same LLVM behind rustc, and the same four bytes at the start of a function that keeps a frame pointer; what those four bytes set up is the Rust library's The call stack ↗, and what an optimizer does to a function's body — so that its first instruction is anything at all, as on the GCC -O2 build here — is its What the optimizer does ↗. A stripped Rust binary is the same problem as a stripped C binary, with the same tables to help: LC_FUNCTION_STARTS on a Mac, .eh_frame on Linux, which Rust emits for every function because unwinding is how a panic travels.

Python. Every Python function begins with the same instruction too, and nothing ever has to search for it:

Verified output of function_start_patterns_py.py — regenerated by tools/run_examples.py, never hand-typed.

twice   starts at offset 0 with RESUME
orphan  starts at offset 0 with RESUME

A function is an object that holds its own code, and the code starts at offset zero of that object; RESUME is the first instruction of every function CPython has compiled since 3.11, and the bytecode after it refers to its locals by name. There is no stripped form of a Python function, and no orphan: a function nobody calls is still an attribute of its module, found by asking.

ABAP. (Not machine-checked — CI cannot run ABAP.) Nothing to search for either. A FORM or a method is an object in the repository with a name, and the generated load is regenerated from the source; where a routine starts is a question the system can always answer, because it keeps the source that says so. The problem on this page — code with no names, and no record of where it begins — is one an ABAP system is not built to have.

See also