Pattern-based decompiler for Go binaries. Recovers original Go source code by matching known compiler output patterns against disassembled machine code.
Unlike classical decompilers that try to reverse-engineer what was lost during compilation, godecompose identifies known patterns in the assembly output and maps them back to source code. It works especially well for Go because Go binaries are statically linked, include symbol tables by default, and the pclntab section provides exact function boundaries and names.
Example
Take this Go program that uses fmt, strings, and os:
// main.go package main import ( "fmt" "os" "strings" ) func greet(name string) string { s := fmt.Sprintf("hello, %s!", name) return strings.ToUpper(s) } func main() { for _, name := range os.Args[1:] { fmt.Println(greet(name)) } }
Build it
$ GOOS=linux GOARCH=amd64 go build -o app .Disassemble it
$ godecompose disassemble app Decoded 180725 instructions Functions: 1966 total runtime: 1414 (skipped) stdlib: 219 (skipped) user: 2 ← only your code matters other: 331 User functions: main.greet @ 0x4a11a0 (blocks: 8) main.main @ 0x4a1220 (blocks: 9)
Decompile it
$ godecompose decompile myapp ./app [1/5] disassembling... [2/5] recovering functions... [3/5] filtering user instructions (2 functions)... [4/5] pattern matching (523 instructions, 362 patterns)... === Decompilation Summary === Go module: myapp Patterns: 362 loaded, 14 matches Packages: 1 main (2 functions) === Recovery Metrics === Instructions: 423 matched / 523 total (80.9%) Functions: 2 with signatures / 2 total Structs: 0 with fields / 0 total Call sites: 12 identified / 18 total (66.7%) Project written to: myapp/
Each recovered line maps directly to a pattern in the database. The generated source code is written to the output directory, with function bodies reconstructed from matched patterns.
Project output
myapp/ ├── go.mod ├── main.go ← package main with reconstructed main() and greet()
Since greet is lowercase (unexported), the callgraph analysis determines it must belong to the main package — the only package where it can be called. Struct types and their methods are similarly placed in the correct package based on method callers.
For programs with multiple packages:
myapp/
├── go.mod
├── main.go ← package main
├── utils/
│ └── utils.go ← recovered utils package with struct definitions
└── models/
└── models.go ← recovered models package with struct definitionsQuick Start
# Install git clone https://github.com/cookiengineer/godecompose cd godecompose go build ./cmd/godecompose/ # Show binary metadata ./godecompose info ./myprogram # Disassemble and show user functions (skipping runtime/stdlib) ./godecompose disassemble ./myprogram # Decompile to a Go project directory ./godecompose decompile myproject ./myprogram # List available patterns ./godecompose patterns list # Validate a pattern file ./godecompose patterns validate path/to/file.hexpat # Discover patterns from a Go source file ./godecompose patterns discover source.go
How It Works
1. Parse the binary
Supports ELF (Linux), PE (Windows), and Mach-O (macOS) formats for x86_64. Extracts sections, symbol tables, Go build info, and the pclntab (PC-line table) that gives exact function boundaries.
2. Disassemble with symbol resolution
Uses golang.org/x/arch/x86/x86asm to decode x86_64 machine code. Builds a SymLookup from the binary's symbol table so that CALL instructions resolve to human-readable names like CALL fmt.Fprintln(SB) instead of CALL 0x49fc25.
3. Recover and classify functions
The pclntab provides exact function entry points. Symbol names are classified:
- User code:
main.*, or functions matching the detected module prefix - Runtime:
runtime.*,type:.*, and internal Go symbols - Stdlib:
fmt.*,sync.*,os.*,net.*,encoding/*, etc.
Only user code is decompiled. Runtime and stdlib are skipped.
Method receivers (e.g. (*IntHeap).Push or User.Method) are parsed from symbol names. Callgraph analysis then refines package placement: lowercase (unexported) functions are reassigned to their caller's package using Go visibility rules, and struct types are placed based on the consensus of their methods' packages.
4. Match patterns
The pattern database (350+ patterns across all Go stdlib packages) describes known compiler output sequences:
// patterns/libs/golang/highlevel/highlevel.hexpat pattern fmt.Printf { name: "fmt.Printf"; instr match { CALL fmt_Fprintf // matches CALL fmt.Fprintf(SB) in disassembly } gen { fmt.Printf($format, $args...); } bind { format as "fmt"; args as "args"; } }
Patterns match by:
- CALL target name: fuzzy-matches
CALL fmt_FprintfagainstCALL fmt.Fprintf(SB)orCALL fmt.(*pp).doPrintf(SB) - Instruction sequence: matches multi-instruction patterns like the
runtime.memmoveforward/backward copy sequence - Operand capture: extracts register names and immediate values as variables for source reconstruction
5. Generate source code
Matched patterns expand their gen templates with captured variable bindings. Unmatched instruction ranges are emitted as assembly comments.
6. Write project (optional)
Functions are grouped by their Go package path (extracted from symbol names like myprogram/utils.Greet). A main.go is generated for the entry point, and sub-package directories are created for each recovered package. Recovered struct types emit stub definitions with method receiver syntax (func (r *Type) Method() { ... }).
Requirements
| Requirement | Details |
|---|---|
| Architecture | x86_64 (amd64) |
| Binary format | ELF, PE, or Mach-O |
| Symbol table | Required for high-level patterns. Go binaries include this by default (go build). Stripping with -ldflags="-s" removes symbols. |
| Debug info | Not needed. DWARF sections are unused. |
| Go version | Works with Go 1.16+ binaries (pclntab format evolved but is handled) |
Pattern Language
Godecompose uses an ImHex-compatible pattern description language with extensions for decompilation. Pattern files use the .hexpat extension.
Basic structure
arch x86_64; platform linux, darwin; pattern my_pattern { name: "display name"; library: "identifier"; description: "what this matches"; // Assembly view: what instructions to match instr block_name { MOVQ src, dst // capture variables: src, dst CALL function_name // match by symbol name } // Source view: what to generate gen { myFunction($dst, $src); } // Variable renaming bind { src as "source"; dst as "dest"; } }
Pattern types
- CALL patterns (high-level):
CALL fmt_Fprintln— matches any CALL to a function whose symbol containsfmt.Fprintln. Requires symbol table. - Instruction sequence patterns (low-level): Multi-instruction sequences with register capture. Works even without symbol table.
- Alternative patterns: Use
|for alternative instruction sequences within the same pattern.
Project Structure
godecompose/
├── cmd/godecompose/ # CLI tool
├── actions/ # Reusable decompilation pipeline steps
├── types/ # Arch, Platform enums
├── binary/ # Binary format interface + Open() dispatcher
│ ├── elf/ # ELF parser
│ ├── pe/ # PE/COFF parser
│ └── macho/ # Mach-O parser
├── disasm/ # x86_64 disassembler + CFG builder
│ └── goasm/ # Go Plan 9 assembly dialect support
├── function/ # Function recovery (pclntab, classification, callgraph, structs)
├── pattern/
│ ├── lang/ # Pattern language engine (lexer/parser/AST/evaluator)
│ ├── matcher/ # Instruction pattern matcher
│ └── generate/ # Source code + project generator
├── database/ # Pattern database loader + syscall tables
│ └── syscall/ # Syscall table types
├── patterns/ # Pattern files (.hexpat) + syscall tables (.json)
│ ├── kernels/ # Linux, Windows, Darwin, FreeBSD syscall tables
│ └── libs/golang/ # Go runtime + stdlib decompilation patterns
├── docs/ # Technical documentation
├── e2e/ # End-to-end integration tests
├── testdata/ # Test Go programs for cross-compilation
└── goutil/ # Test helpers (cross-compilation)
Contributing
Adding new patterns
- Create a
.hexpatfile in the appropriate directory underpatterns/libs/golang/ - Write patterns using the pattern language (see existing files for examples)
- Use
./godecompose patterns discover source.goto auto-generate stub patterns from compiler output - Validate:
./godecompose patterns validate path/to/file.hexpat - Write an E2E test that compiles a Go program using the functions and verifies pattern matches
Adding syscall tables
Edit the JSON files in patterns/kernels/<os>/syscall_table.json and add entries:
{"number": 123, "name": "syscall_name", "args": "arg descriptions", "returns": "return type"}License
AGPL-3.0