Every analysis that the Glass GUI performs is also exposed as a CLI
verb that emits structured JSON to stdout. The same glass binary
that opens the GUI is the automation entry point — pick a subcommand
and you get a one-shot, scriptable result.
This document covers every automation verb. Legacy text-output
subcommands (arm64, bundle, gui, db-inject-tab, string-comments,
plt-probe, hash-bench, cfg) predate the automation API and are
not described here; run glass help <name> for those.
Output. JSON by default. Pass --text for a human-readable
rendering — useful when you're reading at the terminal rather than
piping to jq.
glass inspect ./app.apk # JSON
glass inspect ./app.apk --text # human-readableEnvelope. Every JSON response has shape { "data": ..., "meta": { "duration_ms": ... } }.
Errors go to stderr in the parallel shape { "error": { "message": ... } }
and the process exits non-zero.
Addresses. Always hex strings ("0x1000058d4"), never raw
numbers — JS / jq lose precision past 2^53.
Artifact references. Verbs that take --artifact accept either
the artifact's full label (arm64-v8a/libnative.so, glass, the
framework name for an IPA) or any hex prefix of its content-hash ID
(f46f, f46fac70…). Use glass artifacts <path> to enumerate
them.
Class references. Verbs that take --class accept either JNI
form (Lcom/example/Foo;) or Java form (com.example.Foo).
Method references. Method keys are in smali form:
Lclass;->name(descriptor)return. method-calls accepts a bare
name when unambiguous; otherwise pass name(descriptor).
| Flag | Meaning |
|---|---|
--text |
Render automation verbs as text instead of JSON |
--fresh |
(GUI only) ignore persisted tab / expansion state |
--help |
Show the subcommand list |
Kind (apk / ipa / native), label, content hash, and one line
per artifact (id, size, architecture, section count).
glass inspect ./app.apk --textSame artifact rows as inspect, no bundle header.
Per-artifact section table (name, kind, address, size, bytes-on-disk).
--artifact narrows to one.
Per-artifact format / architecture / section count / symbol-count hint.
Content-hash a file in isolation — returns artifact_id, byte size,
elapsed time. Replaces the old hash-bench for benchmarking.
glass hash ./libfoo.so
# {"data":{"artifact_id":"f46f…","size_bytes":12345678,"duration_ms":42}, …}Lists symbols across one or all artifacts. --filter is a
case-insensitive substring match on the demangled name. --kind is
one of function, object, other. --limit caps results per
artifact.
glass symbols ./libfoo.so --filter init --kind function --limit 20Symbol covering / at a hex address. addr can be with or without
the 0x prefix. Returns null when no symbol covers the address.
glass symbol-at ./libfoo.so 0x1000058d4 --artifact libfoo.soRun one symbol name through the C++/Rust/Swift demangler. No bundle required.
glass demangle _ZN5glass4mainELinear-sweep disassembly of a text section. When --section is
omitted, picks the first text section in the artifact. Each row
includes address, raw bytes, mnemonic, operands, the covering
symbol (if any), and a resolved branch / ADRP target comment.
glass disasm ./libfoo.so --artifact libfoo.so --section .text --limit 100Decode one 32-bit AArch64 word. word is hex; addr (default 0)
matters for PC-relative branches. No bundle required.
glass decode 0x52800000 # mov w0, #0
glass decode 0x94000003 --addr 0x100000Block list + edges + layout for one function. --func accepts a
hex address or an exact symbol name.
glass cfg-of ./libfoo.so --artifact libfoo.so --func "glass::main"Returns:
entry_address,end_addressblocks[]— id, start/end address, instruction count, call count, rank, x-coordinate (for layout),exits_functionflagedges[]— from / to block id, kind (Fallthrough/ConditionalTaken/Unconditional/Call/Return)
Every call site inside a function. Lighter than cfg-of if you
only want the outbound call list.
glass calls-from ./libfoo.so --artifact libfoo.so --func _main --textAll DEX classes. --package filters by JNI or Java prefix.
glass classes ./app.apk --package androidx.annotation. --textFull smali source for one class.
glass smali ./app.apk --class com.example.MainActivity --textStage a typed rewrite of one DEX class. The body is the full smali
text (same shape smali returns); the .class line in the body
must declare the same class as --class (we cross-check to avoid
silently overwriting the wrong slot).
Exactly one of --body (inline string), --file (path to a
.smali file), or stdin (neither flag given) supplies the body.
Smali edits accumulate in the same patch file as byte-level
patch edits — export-patched writes both. Per-(artifact, class_jni) upsert: re-staging the same class replaces the prior
body.
# Round-trip via stdin
glass smali ./app.apk --class com.example.Foo \
| sed 's/old-name/new-name/g' \
| glass smali-set ./app.apk --class com.example.Foo --patches edits.json
# Or read from disk
glass smali-set ./app.apk --class com.example.Foo \
--file new_foo.smali --patches edits.jsonMethods declared by a class (name, descriptor, modifiers, op count, constructor flag).
Fields declared by a class (name, type, modifiers).
Every invoke-* call site inside a method. --method is either a
bare name (first match) or name(descriptor) for unambiguous lookup.
glass method-calls ./app.apk --class com.example.Foo --method 'bar(Ljava/lang/String;)V'The xref verbs build their indices inline for each query — the CLI is one-shot, so there's no cache to amortise. For sustained interactive use, the GUI's incremental indices are faster.
Native callers and address-takes pointing at <addr> inside one
artifact's text sections. Catches direct branches, ADRP+ADD pairs,
and other PC-relative references.
glass xref-addr ./libfoo.so --artifact libfoo.so 0x1000058d4Same as xref-addr, but accepts a symbol name. Convenience wrapper.
glass callers ./libfoo.so --artifact libfoo.so --symbol "glass::main"DEX methods that invoke-* the given method key (in smali form,
Lclass;->name(descriptor)return).
glass dex-callers ./app.apk --method 'Lcom/example/Foo;->bar()V'DEX methods that read or write the given field
(iget* / iput* / sget* / sput*).
glass field-refs ./app.apk --field 'Ljava/lang/System;->out:Ljava/io/PrintStream;'Case-insensitive substring search across native symbols and DEX
class / method / field names. Each hit records its kind, label,
context, and a jump target (hex address for native, JNI form
for DEX).
glass search ./app.apk "onCreate" --limit 20Printable-ASCII NUL-terminated strings from a native artifact's
non-text non-debug sections. Default --min is 4.
glass strings ./libfoo.so --artifact libfoo.so --min 8 --limit 50Scan every text + data section of one artifact for a byte
pattern. Atoms are whitespace-separated; each is either a
2-character byte mask (c0, 0xc0, e?, ?f, ??) or a
bounded gap (* = 0..=32 bytes default, *(min..max) to
override).
# `mov w0, #1 ; ret` (returning-true stub finder)
glass bin-search ./libfoo.so --artifact libfoo.so \
--pattern '20 00 80 52 c0 03 5f d6'
# any ADRP+ADD pair with no intervening bytes
glass bin-search ./libfoo.so --artifact libfoo.so \
--pattern '?? ?? ?? 9? ?? ?? 4? 91'
# raw data: find embedded magic
glass bin-search ./libfoo.so --artifact libfoo.so --pattern 'de ad be ef'Matches don't span sections. Each result carries a preview
column: two decoded AArch64 instructions joined with ; for
text sections (e.g. mov x0, #0 ; ret x30), or the first 8
bytes as space-separated hex for data sections.
Full pattern reference + worked examples: docs/BinSearch.md.
Higher-level than bin-search: write the assembly and let
Glass compile it to bytes (with operand-bit masking for any
wildcards) before scanning. Mnemonics, register names,
immediates, and ;-separated multi-instruction sequences are
supported. Wildcards let you express a code shape without
pinning every operand:
| Token | Meaning |
|---|---|
* |
any operand (kind inferred from the chosen opcode) |
#* |
any immediate (hints the opcode picker) |
x |
any X-class register |
w |
any W-class register |
<*>, <X>, <W>, <R>, <imm> |
same as the shorthand forms, useful when nested inside other syntax ([x, #*], etc.) |
r |
any ARMv7 R-class register (r0..r15) |
The pattern grammar dispatches on the target artifact's
architecture: AArch64 artifacts (arm64-v8a, arm64) accept
AArch64 syntax; ARMv7 artifacts (armeabi-v7a) accept ARMv7
syntax. Thumb is tried first per instruction; ARM mode (A32) is
the automatic fallback when no Thumb form matches.
# every `mov w0, #N` site, whatever N is (AArch64)
glass insn-search ./libfoo.so --artifact libfoo.so \
--pattern 'mov w0, #*'
# any ADRP into x1 followed immediately by ADD into the same reg (AArch64)
glass insn-search ./libfoo.so --artifact libfoo.so \
--pattern 'adrp x1, * ; add x1, x1, #*'
# every `ret x30` (AArch64 — concrete, no wildcards)
glass insn-search ./libfoo.so --artifact libfoo.so --pattern 'ret'
# ARMv7: every `mov r1, rX` for any X
glass insn-search ./libtool-checker.so --artifact libtool-checker.so \
--pattern 'mov r1, <R>'
# ARMv7: every conditional `bxeq lr`
glass insn-search ./libtool-checker.so --artifact libtool-checker.so \
--pattern 'bxeq lr'
# ARMv7: every push of {r4..r7, lr}
glass insn-search ./libtool-checker.so --artifact libtool-checker.so \
--pattern 'push {r4-r7, lr}'Known ARMv7 limitations (current scope): shifted-operand forms
(r0, lsl #2), pre/post-index addressing ([rN, #imm]!,
[rN], #imm), register-offset memory ([rN, rM]), and
bitmask-syntax register lists ({0b00010010}) aren't supported
yet. Register-list range syntax ({r4-r7}) works.
The response includes bytes_hex showing the compiled
byte-mask atoms (e.g. 01/1f ?? ?? 90/9f for adrp x1, *) so
you can see exactly which bits are pinned vs wildcarded. Match
rows reuse the bin-search shape — section, address, length,
preview.
Captures (<name:kind> cross-referencing the same wildcard
later in the pattern) are designed but not yet implemented;
track on the roadmap.
Full design + phasing: docs/InsnPattern.md.
Glass persists window state, open tabs, and user annotations in a
content-addressed redb database. Annotations follow the artifact
(blake3 hash of the bytes), so the same libfoo.so shipped in two
different APKs shares analysis state.
Each annotation slot — keyed by (artifact, key) — carries up to
three independent facets: a rename (display name override), a
comment (free-form note), and a colour (RGBA tint). Writes
merge: setting a comment on a key that already has a rename leaves
the rename intact.
Read all annotations for the artifact identified by content-hashing
<path>. Each entry shows the populated facets — rename /
comment / colour (any combination).
Persist a display name. --key-kind is one of:
address—Vis a hex VA, e.g.0x1000058d4. Most specific.symbol—Vis the symbol display name (glass::main).class—Vis a class JNI (Lcom/example/Foo;).method—Vis the class JNI;--methodis thename(descriptor)returnpart, e.g.bar(Ljava/lang/String;)V.method-line—Vis the class JNI;--methodisname(descriptor)return#<line_offset>(line offset is 0-indexed from the.methoddirective, so0targets the header itself,1+ targets a body line). This is the key the GUI writes when you right-click a specific line inside a smali method body.
glass set-rename ./libfoo.so --key-kind address --key 0x1000058d4 --name decode_packetFree-form note on the same key. --body is multi-line OK.
glass set-comment ./libfoo.so --key-kind symbol --key "glass::main" \
--body "entrypoint after rustc demangle"RGBA hex (8 digits, with or without 0x). UI renders this as a row /
node tint.
glass set-colour ./libfoo.so --key-kind address --key 0x1000058d4 --rgba ff0000aaRemove every facet hung off the key. No-op if nothing's stored.
Reads the bundle-level record for the file at <path>: label,
schema version, last-opened time, artifact count, open tabs,
expanded paths, and the source path. Returns record: null when
the bundle has never been opened.
Glass can edit instructions and bytes inside a loaded artifact and re-pack the whole bundle. The CLI/MCP path uses a JSON patch file that accumulates edits across calls, mirroring the GUI's in-memory edit registry.
Stage one edit. --insn compiles AArch64 assembly with PC-relative
encoding at --addr (no symbol lookup yet — pass hex for branch
targets). --bytes writes raw hex pairs of the same length as the
original at the address. Same (artifact, addr) appearing twice
replaces the earlier edit.
# Replace `svc #0` at 0x100000f7c with `nop`.
glass patch ./libfoo.so --artifact libfoo.so \
--addr 0x100000f7c --insn 'nop' --patches /tmp/p.json
# Set the first 4 bytes at 0x100001000 to a NOP.
glass patch ./libfoo.so --artifact libfoo.so \
--addr 0x100001000 --bytes '1f 20 03 d5' --patches /tmp/p.jsonApply the patch file to the bundle. Mach-O / ELF / .so
standalone binaries are written directly; APK/AAB get re-packed via
the smali zip writer; IPA gets re-streamed via the zip crate.
Empty patch files are rejected.
glass export-patched ./libfoo.so --patches /tmp/p.json --out ./libfoo-patched.soPrint the JSON Schema (draft 2020-12) describing the patch file format. Useful when consuming the file from another tool.
glass patch-schema | jq .{
"version": 1,
"source_path": "/abs/path/to/bundle.so",
"edits": [
{
"artifact": "<64-char hex artifact id from `glass inspect`>",
"vaddr": 4294971260,
"kind": "Instruction",
"new_bytes": [31, 32, 3, 213],
"original_bytes": [],
"source_text": "nop"
}
]
}kind is display-only (one of Instruction, Bytes, String);
the splice writes new_bytes regardless. original_bytes and
source_text are optional and informational. See glass patch-schema for the authoritative description.
The JSON shape is stable and addresses-as-strings are safe for
jq. Some patterns:
# Extract just the addresses of all `init`-named symbols:
glass symbols ./libfoo.so --filter init --limit 20 \
| jq -r '.data[].symbols[].address'
# Every basic block of glass::main in one line per block:
glass cfg-of ./libfoo.so --artifact libfoo.so --func "glass::main" \
| jq -c '.data.blocks[]'
# Strings ≥ 16 chars, sorted by section:
glass strings ./libfoo.so --artifact libfoo.so --min 16 \
| jq -r '.data.strings[] | "\(.section)\t\(.address)\t\(.value)"'
# Find DEX callers of every onCreate override:
for m in $(glass search ./app.apk onCreate \
| jq -r '.data.hits[] | select(.kind=="method") | .jump'); do
glass dex-callers ./app.apk --method "$m" --text
doneFailures emit a JSON object on stderr and exit with code 1:
{"error":{"message":"no artifact matches \"libnope.so\""}}With --text, the same message goes to stderr as error: <msg>.
Stdout is empty on failure, so glass ... | jq will simply produce
no output rather than swallowing a malformed line.
Two helper verbs expose the automation API to LLM tooling:
Prints the machine-readable catalog: one entry per verb with name, description, JSON Schema for arguments, and an example invocation. Useful for generating prompts, building external clients, or verifying the surface programmatically.
glass skills | jq '.skills[] | {name, example}'Runs an MCP (Model Context Protocol) stdio server. Every verb in
this document becomes an LLM-callable tool with the schema shown
by glass skills. Plug into Claude Desktop, Cursor, Zed, or any
other MCP host.
glass mcp # speak JSON-RPC over stdin/stdoutFor Claude Desktop, add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"glass": { "command": "/usr/local/bin/glass", "args": ["mcp"] }
}
}Tool results come back as a single text content block whose body
is the same { data, meta } JSON envelope the CLI emits — parse
the .content[0].text field as JSON.
docs/AutomationAPI.md— design notes for the capability surface (glass-apicrate) that backs every verb.docs/Roadmap.md— what's planned next.