Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/bennn/zordoz

Manipulating racket bytecode
https://github.com/bennn/zordoz

racket

Last synced: 1 day ago
JSON representation

Manipulating racket bytecode

Awesome Lists containing this project

README

        

Zordoz
======
[![Build Status](https://travis-ci.org/bennn/zordoz.svg)](https://travis-ci.org/bennn/zordoz)
[![Coverage Status](https://coveralls.io/repos/bennn/zordoz/badge.svg?branch=master&service=github)](https://coveralls.io/github/bennn/zordoz?branch=master)
[![Scribble](https://img.shields.io/badge/Docs-Scribble-blue.svg)](http://docs.racket-lang.org/zordoz/index.html)

[ZORDOZ](https://www.youtube.com/watch?v=kbGVIdA3dx0) speaks to you! His chosen ones.

This is an explorer for Racket .zo files.

Tested to work on Racket `v6.5`
For compatibility with older versions, see the
[v6.1](https://github.com/bennn/zordoz/tree/v6.1)
and
[v6.2](https://github.com/bennn/zordoz/tree/v6.2)
and
[v6.3](https://github.com/bennn/zordoz/tree/v6.3)
branches of this repo.
(Note that installing through `raco` will choose the available version, if any, matching your Racket install.)

Typed Racket users can `(require zordoz/typed)` for type-annotated bindings from `zordoz`.
Also, `zordoz/typed/zo-structs` is a safe wrapper around Racket's [compiler/zo-structs](http://docs.racket-lang.org/raco/decompile.html#%28mod-path._compiler%2Fzo-structs%29) library.

Install
-------

You have two options.

1. Install from `raco` by running `raco pkg install zordoz`
2. Build from source by cloning this repo and using `raco`:
`git clone https://github.com/bennn/zordoz; raco pkg install zordoz/`

To run tests, do `raco test zordoz`.
Tests are located in the `test` submodule of each source file.

Usage
-----

Zordoz provides a `raco zordoz` command.

### REPL

Activate the REPL by giving a path to a compiled file.

```
raco zordoz FILE.zo
```

Passing the `-t` option uses typed racket code.
Beware, the typed racket version is up to 5x slower than untyped because of contracts with the `compiler/zo-lib` structs.

The REPL accepts the following commands:

- `alst` prints all command aliases; for example, the repl treats the words 'alst' and 'aliases' the same way
- `back` goes back to the previous context
- `dive ARG` changes context to a new zo struct or list (other dives are not permitted)
- `find ARG` searches for matches to `ARG` and, if successful, changes context to the list of results
- `help` prints information about these commands
- `info` prints data about the current context
- `jump` reverts to a previously saved context
- `save` marks the current context as a target for `jump`
- `quit` exits the interpreter

The functions implementing the `dive`, `find`, and `info` commands are available outside the REPL.
Check the [guide](http://bennn.github.io/zordoz) for a summary.

### Quick Search

Running:

```
./zordoz -f branch -f lam -f closure FILE.zo
```

Will count and print the number of times the zo structs [branch](http://docs.racket-lang.org/raco/decompile.html#%28def._%28%28lib._compiler%2Fzo-structs..rkt%29._branch%29%29) [lam](http://docs.racket-lang.org/raco/decompile.html#%28def._%28%28lib._compiler%2Fzo-structs..rkt%29._lam%29%29) and [closure](http://docs.racket-lang.org/raco/decompile.html#%28def._%28%28lib._compiler%2Fzo-structs..rkt%29._closure%29%29) appear.
This may take a while, depending on the size of the bytecode file.
You can limit the search depth by passing a natural number with the `-l` flag.

See the [decompilation guide](http://docs.racket-lang.org/raco/decompile.html#%28mod-path._compiler%2Fzo-structs%29) for a list of all zo struct names.

Background
----------

Racket bytecode is stored in files with a `.zo` [extension](http://docs.racket-lang.org/raco/make.html).
This tool makes it easier to explore the bytecode representation of a file, whether or not you have access to the file's source code.

Given a `.zo` file, we decompile the bytecode into a struct (aka, a "zo-struct") using Racket's built-in [decompilation API](http://docs.racket-lang.org/raco/decompile.html).
The REPL loads this struct as its initial _context_ and begins accepting commands, making it easy to visualize and explore Racket bytecode.

Example
-------

Suppose we create and compile a small racket file:
```
> echo -e "#lang racket/base\n(if #t (+ 1 1) 0)" > test.rkt
> raco make test.rkt
```

The actual bytecode is not human readable.
Neither is the struct representation output by `zo-parse`:
```
> echo -e '#lang racket/base\n(require compiler/zo-parse)\n(call-with-input-file "compiled/test_rkt.zo"\n (lambda (fd) (displayln (zo-parse fd))))' > print-test.rkt
> racket print-test.rkt
#s((compilation-top zo 0) 0 #s((prefix zo 0) 0 (#f) ()) #s((mod form 0 zo 0) test test # #s((prefix zo 0) 0 (#s((module-variable zo 0) # print-values 0 0 #s((function-shape zo 0) #(struct:arity-at-least 0) #f))) ()) ((0 () ()) (1 () ()) (#f () ())) ((0 #) (1) (-1) (#f)) (#s((apply-values expr 0 form 0 zo 0) #s((toplevel expr 0 form 0 zo 0) 0 0 #t #t) 2)) () ((0 () ())) 0 #s((toplevel expr 0 form 0 zo 0) 0 0 #f #f) #f #t () (#s((mod form 0 zo 0) (test configure-runtime) configure-runtime # #s((prefix zo 0) 0 (#s((module-variable zo 0) # configure 0 0 #s((function-shape zo 0) 1 #f))) ()) ((0 () ()) (1 () ()) (#f () ())) ((0 # #) (1) (-1) (#f)) (#s((application expr 0 form 0 zo 0) #s((primval expr 0 form 0 zo 0) 1000) (#t))) () ((0 () ())) 1 #s((toplevel expr 0 form 0 zo 0) 0 0 #f #f) #f #t () () ())) ()))
```

ZORDOZ offers a more readable presentation.
Below is a sample interactive session with the same small file (interspersed with commentary):

```
> racket zordoz.rkt compiled/test_rkt.zo
INFO: Loading bytecode file 'compiled/test_rkt.zo'...
INFO: Parsing bytecode...
INFO: Parsing complete!
--- Welcome to the .zo shell, version 0.1 'outlands' ---
zo> info

max-let-depth : 0
prefix :
code :
```

The `compilation-top` struct is at the top of most every `.zo` file.
Things get more interesting as we explore the structs nested inside it.

```
zo> dive code
zo> info

name : test
srcname : test
self-modidx : #
prefix :
provides : 0 [] [] 1 [] [] #f [] []
requires : 0 # 1 -1 #f
body :
syntax-bodies :
unexported : 0
max-let-depth : 0
dummy :
lang-info : #f
internal-context : #t
flags :
pre-submodules : [1]
post-submodules : []
```

The `mod` struct represents a Racket module.
This module has the name `test`; inferred from our filename `test.rkt`.

We could continue `dive`-ing into structs, or we can use the shell's `find` command to look for structs matching a name like `mod` or `compilation-top`.
Let's search for `branch` structs.
Maybe we can find the `if`-statement in our original code.

```
zo> find branch
FIND returned 0 results
```

Nothing.
The `if`-statement has been optimized away.
Let's try to find what it turned into by searching the body of the module.

```
zo> dive body
zo> info
()[1]
```

The syntax `()[LENGTH]` denotes a list of zo-structs.
`LENGTH` is the number of elements in the list--we can `dive` into any valid index.

```
zo> dive 0
zo> info

proc :
args-expr : 2
```

Looks like our `if`-statement was optimized into a constant, `2`.

Happy exploring!