dynasm DynASM with Lua mode

local dynasm = require'dynasm'

This is a modified version of DynASM that allows generating, compiling, and running x86 and x86-64 assembly code directly from Lua. It also exposes the DynASM assembler and linker to be used as Lua modules.

Jump To: Examples | DynASM API | DASM API | Changes to DynASM | Instructions | Directives

Features

translate, compile and run Lua/ASM code from Lua (no C glue)

load Lua/ASM (.dasl) files with require()

works with file, string and stream inputs and outputs

Before you start

DynASM is not an inline assembler, it’s a code generator. The following code: function codegen ( Dst ) codegenDst for i = 1 , 3 do | mov ax , i mov ax end end does not run the assembly instruction 3 times when codegen is called, instead, it merely adds the instruction sequence mov ax, 1; mov ax, 2; mov ax, 3 to the dynasm state Dst when codegen is called. Mixing Lua and ASM code like this has the effect of generating code, not running it. DynASM has two parts: the assembler/preprocessor, written in Lua, and the the linker/encoder, written in C. dynasm.lua is the preprocessor. It takes mixed C/ASM code as input (from a file, string or file-like object) and generates C code (to a file, string, or file-like object). Alternatively, it can take mixed Lua/ASM code (like the above example) and generate Lua code, which is what the “Lua mode” part is all about. dasm.lua is the binding to the C part of DynASM (the linker/encoder) which deals with building the code into executable memory that can be called into. .dasl files refer to Lua/ASM files, .dasc files refer to C/ASM files. dasl files can be used transparently as Lua modules (they are translated on-the-fly).

Examples

1. Self-contained module

This simple, self-contained module publishes the function multiply(x, y) -> x * y.

multiply_x86.dasl:

local ffi = require 'ffi' --required ffi local dasm = require 'dasm' --required dasm |. arch x86 --must be the first instruction arch x86 |. actionlist actions --make an action list called `actions` actionlist actions local Dst = dasm . new ( actions ) --make a dasm state Dstdasmnewactions -- the next chunk of asm code will be added to the action list, and a call -- to `dasm.put(Dst, 0)` will be generated in its place, which will be copying -- the code from the start of the action list into the Dst state. | mov eax , [ esp + 4 ] mov eaxesp | imul dword [ esp + 8 ] imul dwordesp | ret ret local code = Dst : build () --check, link and encode the code codeDstbuild local fptr = ffi . cast ( 'int32_t __cdecl (*) (int32_t x, int32_t y)' , code ) --take a callable pointer to it fptrfficastcode return function ( x , y ) local _ = code --pin the code buffer so it doesn't get collected code return fptr ( x , y ) fptr end

The best way to understand how the above code is supposed to work is to translate it:

> lua dynasm.lua multiply_x86.dasl

main.lua :

require 'dynasm' --hook in the `require` loader for .dasl files local multiply = require 'multiply_x86' --translate, load and run `multiply_x86.dasl` multiply assert ( multiply (- 7 , 5 ) == - 35 ) multiply

2. Code gen / build split

This is an idea on how you can keep your asm code separated from the plumbing required to build it, and also how you can make separate functions out of different asm chunks from the same dasl file.

funcs_x86.dasl :

local ffi = require 'ffi' ffi local dasm = require 'dasm' dasm |. arch x86 arch x86 |. actionlist actions actionlist actions |. globalnames globalnames globalnames globalnames local gen = {} gen function gen . mul ( Dst ) --function which generates code into the dynasm state called `Dst` genmulDst |-> mul : --and returns a "make" function which gets a dasm.globals() map mul | mov eax , [ esp + 4 ] --and returns a function that knows how to call into its code. mov eaxesp | imul dword [ esp + 8 ] imul dwordesp | ret ret return function ( globals ) globals return ffi . cast ( 'int32_t __cdecl (*) (int32_t x, int32_t y)' , globals . mul ) fficastglobalsmul end end function gen . add ( Dst ) genaddDst |-> add : add | mov eax , [ esp + 4 ] mov eaxesp | add eax , dword [ esp + 8 ] add eaxdwordesp | ret ret return function ( globals ) globals return ffi . cast ( 'int32_t __cdecl (*) (int32_t x, int32_t y)' , globals . add ) fficastglobalsadd end end return { gen = gen , actions = actions , globalnames = globalnames } gengenactionsactionsglobalnamesglobalnames

funcs.lua :

local dynasm = require 'dynasm' dynasm local dasm = require 'dasm' dasm local funcs = require 'funcs_x86' funcs local state , globals = dasm . new ( funcs . actions ) --create a dynasm state with the generated action list stateglobalsdasmnewfuncsactions local M = {} --generate the code, collecting the make functions for name , gen in pairs ( funcs . gen ) do namegenfuncsgen M [ name ] = gen ( state ) namegenstate end local buf , size = state : build () --check, link and encode the code bufsizestatebuild local globals = dasm . globals ( globals , funcs . globalnames ) --get the map of global_name -> global_addr globalsdasmglobalsglobalsfuncsglobalnames for name , make in pairs ( M ) do --make the callable functions namemake M [ name ] = make ( globals ) namemakeglobals end M . __buf = buf --pin buf so it doesn't get collected __bufbuf return M

main.lua

local funcs = require 'funcs' funcs assert ( funcs . mul (- 7 , 5 ) == - 35 ) funcsmul assert ( funcs . add (- 7 , 5 ) == - 2 ) funcsadd

3. Load code from a string

local dynasm = require 'dynasm' dynasm local gencode , actions = dynasm . loadstring ( [[ gencodeactionsdynasmloadstring local ffi = require'ffi' local dasm = require'dasm' |.arch x86 |.actionlist actions local function gencode(Dst) | mov ax, bx end return gencode, actions ]])()

4. Translate from Lua

local dynasm = require 'dynasm' dynasm print ( dynasm . translate_tostring 'multiply_x86.dasl' ) dynasmtranslate_tostring

The above is equivalent to the command line:

> lua dynasm.lua multiply_x86.dasl

Tip: You can pre-assemble foo.dasl into foo.lua – require() will then choose foo.lua over foo.dasl , so you basically get transparent caching for free. This speeds up app loading a bit, and you can ship your app without the assembler (you still need to ship the linker/encoder for all the platforms that you support).

5. Included demo/tutorial

Check out the included dynasm_demo_x86.dasl and dynasm_demo.lua modules for more in-depth knowledge about DynASM/Lua interaction. It works on Windows, Linux and OSX, both x86 and x64.

6. Brainfuck JIT compiler

The examples above don’t do DynASM enough justice, because DynASM was after all made for building JIT compilers. The bf project contains a Lua/ASM translation of the code from Josh Haberman’s tutorial on DynASM and JITs, and probably the simplest JIT compiler you could write. It too works on Windows, Linux and OSX, x86 and x64.

DynASM API

hi-level dynasm.loadfile(infile[, opt]) -> chunk load a dasl file and return it as a Lua chunk dynasm.loadstring(s[, opt]) -> chunk load a dasl string and return it as a Lua chunk low-level dynasm.translate(infile, outfile[, opt]) translate a dasc or dasl file dynasm.string_infile(s) -> infile use a string as an infile to translate() dynasm.func_outfile(func) -> outfile make an outfile that calls func(s) for each piece dynasm.table_outfile(t) -> outfile make an outfile that writes pieces to a table dynasm.translate_tostring(infile[, opt]) -> s translate to a string dynasm.translate_toiter(infile[, opt]) -> iter() -> s translate to an iterator of string pieces

DASM API

hi-level dasm.new(

actionlist,

[externnames],

[sectioncount],

[globalcount],

[externget],

[globals]) -> state, globals make a dasm state for an action list.

-> per .actionlist directive.

-> per .externnames directive.

-> DASM_MAXSECTION from .sections directive.

-> DASM_MAXGLOBAL from .globals directive.

-> func(externname) -> addr , for solving extern s

-> void*[DASM_MAXGLOBAL] , to hold globals state:build() -> buf, size check, link, alloc, encode and mprotect the code dasm.dump(buf, size) dump the code using the included disassembler in luajit dasm.globals(globals, globalnames)->{name -> addr} given the globals array returned by dasm.new() and the globalnames list per .globalnames directive, return a table that maps the names to their address. low-level state:init(maxsection) init a state state:free() free the state state:setupglobal(globals, globalcount) set up the globals buffer state:growpc(maxpc) grow the number of available pc labels state:setup(actionlist) set up the state with an action list state:put(state, ...) the assembler generates these calls state:link() -> size link the code and get its size state:encode(buf) encode the code into a buffer state:getpclabel(pclabel[, buf]) get pc label offset, or pointer if buf is passed state:checkstep(secmatch) check code before encoding state:setupextern(externnames, getter) set up a new extern handler

Changes to DynASM

The source code changes made to DynASM were kept to a minimum in order to preserve DynASM semantics, make it easy to merge back changes from upstream, and to make it easy to add the Lua mode to other architectures supported by DynASM in the future. As for the user-facing changes, the list is again small:

added -l, --lang C|Lua command line option (set automatically for dasl and dasc files).

command line option (set automatically for dasl and dasc files). asm comments can start with both -- and // in Lua mode.

and in Lua mode. the defines ARCH, OS, X86, X64, WINDOWS, LINUX, OSX are available by default in Lua mode.

the .globals directive generates DASM_MAXGLOBAL in Lua mode.

directive generates DASM_MAXGLOBAL in Lua mode. .type usage is limited in Lua mode: FOO.field , FOO[expr] and FOO[expr].field are ok, but arbitrary expressions like FOO[5].bar[2].baz are not.

usage is limited in Lua mode: , and are ok, but arbitrary expressions like are not. extern foo resolves to ffi.C.foo by default; if foo has no cdef, ffi.cdef'void foo()' is called (i.e. a dummy cdef is made for it - caveat emptor).

Assembler tutorials & ref docs

Last updated: 2 years ago | Edit on GitHub