DynASM consists of several files, some of which are Lua files, and some of which are C.
This file forms the core of the DynASM preprocessor. It takes a DynASM source file
(which is a mixture of assembly and C) file as input, and produces a C file as output,
therein performing the bulk of the conversion from assembly to machine code. Lines from
the DynASM source file that do not start with a vertical bar are copied verbatim to
the output file. Lines that start with one vertical bar can contain assembly instructions
or assembler directives, and will generally be rewritten to C
lines of the form dasm_put(Dst, ...)
. Lines that
start with two vertical bars are treated as C code, but undergo prepreprocessor substitutions
created by .define
, and can be part of
.macro
definitions.
If a Lua interpreter with a bit
library (such as LuaJIT) is present, dynasm.lua can be invoked with a command like:
lua dynasm.lua FLAGS INFILE.dasc
If the presence of such a Lua interpreter cannot be assumed, LuaJIT2's minilua can be built and used to invoke dynasm.lua:
cd LUAJIT2_ROOT/dynasm gcc -o minilua ../src/host/minilua.c ./minilua dynasm.lua FLAGS INFILE.dasc
The available flags are:
Short | Long | Effect |
---|---|---|
-h | --help | Display help text similar to this table. |
-v | --version | Display version and copyright information. |
-o FILE | --outfile FILE | Set the output file name (defaults to stdout if not given). |
-I DIR | --include DIR | Add a directory to the .include search path. |
-c | --ccomment | Use C style /* */ comments for assembler lines in output. |
-C | --cppcomment | Use C++ style // comments for assembler lines in output (the default). |
-N | --nocomment | Suppress assembler lines in output. |
-M | --maccomment | Show macro expansions as comments in output (default off). |
-L | --nolineno | Do not emit C preprocessor #line directives in output (default on). |
-F | --flushline | Do not aggressively coalesce adjacent dasm_put calls in output (default on). |
-D NAME[=SUBST] | Equivalent to .define NAME [, SUBST] . | |
-U NAME | Undefines a prior -D NAME . | |
-P | --dumpdef | Dump defines, macros, etc. Repeat for more output. |
-A ARCH | --dumparch ARCH | Load architecture ARCH and dump description, similar to the instruction listing page. |
If -A ARCH
or .arch ARCH
is used, dynasm.lua will load the architecture module dasm_ARCH.lua.
Furthermore, the architecture module dasm_x64.lua will load the architecture module dasm_x86.lua. This set of two or three Lua files form the complete DynASM
preprocessor for the given architecture. Said files are only required for converting DynASM source files to plain C files; there is no runtime requirement on
them or on a Lua interpreter.
This C header file contains the default definitions for various macros and the type signatures
for various functions, which collectively form the DynASM runtime library. It should be pulled
in by a #include
directive in every C/C++ file that wants to use DynASM to generate machine code.
If custom definitions of any macros are desired, said macros should be defined prior to dasm_proto.h being included.
If multiple files #include
dasm_proto.h, then they should all consistently #define
any
custom macro definitions. For this reason, if DynASM is used from multiple files, it is conventional to write a
header which wraps dasm_proto.h and provides the custom macro definitions.
This C file contains the implementations of the various functions in the DynASM runtime library.
It should be pulled in by a #include
directive in precisely one C/C++ file, and said directive must be preceded
by a #include
directive for dasm_proto.h.
If DynASM is being used for an architecture other than x86 or x64, that architecture's corresponding header should be pulled in instead of dynasm_x86.h, though this documentation does not cover such uses of DynASM in detail.
A selection of macros can be defined at compile-time to modify the behaviour of DynASM. If the default definition for a particular macro isn't appropriate, it should be redefined prior to including any DynASM headers.
If defined (to any value), dasm_x86.h will use multiple byte-granularity writes rather than a single multi-byte write in cases where the destination of the write might not be aligned.
This should be defined when emitting x86/x64 machine code on a host platform other than x86/x64.
If defined (to any value), the implementations of
dasm_link
,
dasm_encode
, and
dasm_checkstep
in
dasm_x86.h will perform additional
sanity checking to ensure that DynASM is being used correctly.
If encoding speed is a concern, it might be desirable to define DASM_CHECKS
during
development and in debug builds, and undefine it otherwise.
#define DASM_EXTERN(ctx, addr, idx, type) 0
This user-defined macro is used by dasm_x86.h when
dasm_encode
needs to encode an extern address, and
must be redefined if extern addresses are used.
The ctx
parameter has the same type as the variable defined by
Dst_DECL
, and will be set to the first argument
to dasm_encode
.
The addr
parameter has type unsigned char*
, and gives the
address of a int32_t
field which is to be written as part of an x86/x64
instruction.
The idx
parameter has type unsigned int
, and gives an
index into the .externnames
array, therein
identifying the particular extern address being written.
The type
parameter is non-zero to indicate that the int32_t
field
being written represents a signed 32-bit relative offset from the end of the instruction
being encoded (i.e. relative to addr+4
). It is zero to indicate an absolute address
x86, or an absolute address in the low 2GB of address space
x64.
The macro should evaluate to a 32-bit integer value, which will subsequently be written
to (int32_t*)addr
.
For example, if extern names are dynamically findable symbols, this might be defined as:
#define DASM_EXTERN(ctx, addr, idx, type) (\ (type) ? (int)((unsigned char*)dlsym(RTLD_DEFAULT, externs[idx]) - (addr) - 4) \ : (int)dlsym(RTLD_DEFAULT, externs[idx]))
#define DASM_FDEF extern
This user-defined macro is used to control the specifiers on all dasm_
functions. For example, Windows applications which embed DynASM within a shared
library might redefine this to either __declspec(dllimport)
or
__declspec(dllexport)
. Conversely, Linux applications which use DynASM
in just one place and don't want to pollute the symbol namespace might redefine
this to __attribute__((visibility("hidden")))
or just static
.
#define DASM_M_FREE(ctx, p, sz) free(p)
This user-defined macro is used by dasm_x86.h in order to
implement dasm_free
and free pieces of memory
previously allocated by DASM_M_GROW
.
The ctx
parameter has the same type as the variable defined by
Dst_DECL
, and will be set to the first argument
to dasm_free
.
On entry, the p
and sz
arguments will match the p
and sz
arguments from the exit of a prior invocation of
DASM_M_GROW
, i.e. p
gives a pointer
to the memory which needs to be freed, and sz
gives the number of bytes
previously allocated at p
.
#define DASM_M_GROW(ctx, t, p, sz, need) \ do { \ size_t _sz = (sz), _need = (need); \ if (_sz < _need) { \ if (_sz < 16) _sz = 16; \ while (_sz < _need) _sz += _sz; \ (p) = (t *)realloc((p), _sz); \ if ((p) == NULL) exit(1); \ (sz) = _sz; \ } \ } while(0)
This user-defined macro is used by dasm_x86.h whenever
memory needs to be allocated or re-allocated - which
dasm_init
always needs to, and various
other dasm_
functions sometimes need to. It should be redefined,
along with DASM_M_FREE
, when the default
C realloc
/free
allocation mechanism isn't appropriate.
The ctx
parameter has the same type as the variable defined by
Dst_DECL
, and will be set to the first argument
to the dasm_
function performing the allocation.
On entry, the p
argument gives an lvalue of type t*
which is sz
bytes in size. In particular, note that sz
can be zero to indicate an allocation operation should be performed (in which case
p
will be NULL
), or non-zero to indicate that a
re-allocation operation should be performed (in which case p
will not
be NULL
).
Upon exit, the p
argument should give an lvalue of type t*
which is at least need
bytes in size, and the sz
argument
should give an lvalue containing the actual number of bytes.
#define Dst_DECL dasm_State **Dst
This user-defined macro affects the signature of all dasm_
functions,
in particular giving the type and name of the first formal parameter. The name of
said parameter must either remain as Dst
, or there must be a preprocessor
macro which redefines Dst
to match the name used here. The type of
said parameter can be anything that allows Dst_REF
to give an lvalue of type dasm_State*
.
By convention, non-trivial users of DynASM that have a collection of state tied
to a dasm_State*
will define a structure containing a dasm_State*
and the related state, and will redefine Dst_DECL
to be pointer to
said structure.
For example, when using DynASM within a C++ project, the following definitions might be used to ensure RAII semantics and to pass other fields to emitter functions:
struct my_dasm_State { my_dasm_State(int maxsection) { dasm_init(this, maxsection); } ~my_dasm_State() { dasm_free(this); } struct dasm_State* ds; /* ... other fields ... */ }; #define Dst_DECL my_dasm_State* Dst #define Dst_REF Dst->ds
#define Dst_REF (*Dst)
This user-defined macro is used by dasm_x86.h to
extract an lvalue of type dasm_State*
from a variable called
Dst
. It should be redefined if, and only if,
Dst_DECL
is also redefined.
The DynASM API consists of 10 functions, which must be used in a fairly strict order:
Phase | Callable Functions |
---|---|
1 | dasm_init |
2 | dasm_setupglobal |
3 | dasm_setup |
4 | Any of dasm_checkstep , dasm_growpc , dasm_put , each zero or more times |
5 | dasm_link |
6 | dasm_encode |
7 | dasm_getpclabel , zero or more times |
8 | dasm_free |
DASM_FDEF int dasm_checkstep(Dst_DECL, int secmatch);
This function can be called after a sequence of calls to dasm_put
to
perform extra sanity checking.
If DASM_CHECKS
is defined, and a prior call to dasm_put
encountered an error, this function will return a non-zero result indicating the error.
If DASM_CHECKS
is defined, and any of the labels 1:
through
9:
have been referenced but not defined, this function will return DASM_S_UNDEF_L|i
,
where i
is the problematic label number. If there are no problems with the labels, then
this function will undefine the labels 1:
through 9:
.
If DASM_CHECKS
is defined, the secmatch
argument is
non-negative, and the index of the section currently being written (see .section
)
isn't equal to secmatch
, then this function will return DASM_S_MATCH_SEC|i
, where
i
is the index of the current section.
Otherwise, this function will return the value 0
to indicate success.
DASM_FDEF int dasm_encode(Dst_DECL, void *buffer);
This function is called after dasm_link
in order to actually generate
machine code.
On entry, the buffer
argument should point to a block of memory whose size in bytes is at
least the size returned by dasm_link
. For the duration of the
call to dasm_encode
, this block of memory must be at least readable and writable.
On exit, the block of memory at buffer
will have been filled with machine code, and the globals
array passed to dasm_setupglobal
will have been populated with
pointers into said block. If the machine code is to be executed (as opposed to just written to disk), then the
block of memory needs to be at least readable and executable after dasm_encode
. Operating system APIs such as
VirtualProtect
(Windows) or mprotect
(POSIX) may be appropriate for marking memory as executable.
This function should always return 0
, but it can return non-zero if a DynASM bug is triggered.
DASM_FDEF void dasm_free(Dst_DECL);
This function frees a DynASM state previously allocated by dasm_init
.
Its behaviour can be modified by redefining DASM_M_FREE
.
DASM_FDEF int dasm_getpclabel(Dst_DECL, unsigned int pc);
This function can be called after dasm_encode
in order to retreive the
address of the label =>pc:
.
On success, the return value is the non-negative offset from the start of the buffer passed to
dasm_encode
to the label =>pc:
.
On error (for example =>pc:
not being defined), the return value is negative.
DASM_FDEF void dasm_growpc(Dst_DECL, unsigned int maxpc);
This function can be called after dasm_setup
and before
dasm_link
in order to increase the number of labels usable
with the =>pc
syntax. In particular, after a call to this function, the labels
=>0
through =>(maxpc-1)
can be used.
DASM_FDEF void dasm_init(Dst_DECL, int maxsection);
This function performs the first stage of initialisation of a DynASM state. Its behaviour can be modified by
redefining DASM_M_GROW
.
If .section
is used, then DASM_MAXSECTION
should be
passed as maxsection
. Otherwise, the value 1
should be passed.
A call to this function must be followed by a call to dasm_setupglobal
to perform the second stage of initialisation, and at some point later dasm_free
should be called to free the DynASM state.
DASM_FDEF int dasm_link(Dst_DECL, size_t *szp);
This function is called prior to dasm_encode
to calculate the size
of the machine code that will be generated. Consumers of the API will typically call dasm_link
,
allocate a suitably sized piece of memory, and then call dasm_encode
.
If DASM_CHECKS
is defined, and a prior call to dasm_put
or dasm_checkstep
encountered an error, this function will return
a non-zero result indicating the error. If DASM_CHECKS
is defined, and
the label =>pc
is used but not defined, this function will return DASM_S_UNDEF_PC|pc
.
In all other cases, this function will return the value 0
to indicate success.
DASM_FDEF void dasm_put(Dst_DECL, int start, ...);
Lines in DynASM source files that start with one vertical bar are translated into calls to dasm_put
by the dynasm.lua preprocessor. Conceptually, calls to this function are appending
some assembly code to the DynASM state which dasm_link
/
dasm_encode
will later turn into machine code.
Calls to this function should not be written by humans; they should only be written by the
dynasm.lua preprocessor. That said, the start
parameter
gives an offset into the action list passed to dasm_setup
,
and the variadic parameter is used to pass concrete values for encoding-time constants in the
original assembly code.
If DASM_CHECKS
is defined, this function performs additional
sanity checking. If any of these checks fail, subsequent calls to dasm_checkstep
and dasm_link
will return a non-zero value.
DASM_FDEF void dasm_setup(Dst_DECL, const void *actionlist);
This function must be called after dasm_setupglobal
to complete the
initialisation of a DynASM state.
The array defined by .actionlist
must be passed as the actionlist
parameter, and will be used by subsequent calls to dasm_put
.
DASM_FDEF void dasm_setupglobal(Dst_DECL, void **gl, unsigned int maxgl);
This function must be called after dasm_init
, and before
dasm_setup
, to perform the second stage of initialisation of a DynASM state.
The ident_MAX
value defined by .globals ident
should be passed as
maxgl
, and a void*
array of maxgl
elements should be passed as gl
.
It is common to use a local variable as said array, for example:
void* globals[ident_MAX]; dasm_setupglobal(&d, globals, ident_MAX);
The array is used as scratch space up until dasm_encode
is called, and then afterwards
the elements of the array give the addresses of ->
labels. Continuing the prior example, the address of
the global label ->bar:
would be given by globals[ident_bar]
.
A wide selection of directives can be used in DynASM source files, though they fall into a small number of categories:
Category | Directives |
---|---|
Features |
.arch ,
.capture ,
.dumpcapture ,
.macro ,
.nop ,
.section ,
.type
|
Reflection |
.actionlist ,
.externnames ,
.globalnames ,
.globals
|
Data |
.align ,
.byte ,
.dword ,
.sbyte ,
.space ,
.word
|
Prepreprocessor |
.define ,
.elif ,
.else ,
.endif ,
.error ,
.fatal ,
.if ,
.include
|
As with instructions, arguments to directives are comma-separated, subject to balanced parentheses
(()
, []
, and {}
). For example, .byte x,y,z,w
has four arguments, while .byte x(y,z), w
has two.
| .actionlist ident
This directive causes the dynasm.lua preprocessor to emit a piece of C code similar to:
static const unsigned char ident[] = { /* ... */ };
This directive must be used exactly once in a DynASM source file, and the array it defines must be
passed to dasm_setup
. The contents of the array reflects the
assembly used in the DynASM source file, though in an unspecified format.
| .align (2|4|8|16|32|64|128|256|word|dword|aword|qword|oword)
This directive causes zero or more bytes containing 0x90
to be emitted by
dasm_encode
, such that the offset of the next byte, relative to
the start of the buffer passed to dasm_encode
, is a multiple
of the argument. Said argument must be a power of two between 2 and 256, or one of the following
type size aliases:
Alias | Acts like |
---|---|
.align word | .align 2 |
.align dword | .align 4 |
.align aword | .align 4 x86 or .align 8 x64 |
.align qword | .align 8 |
.align oword | .align 16 |
| .arch (arm|mips|ppc|x64|x86)
This directive specifies the architecture of the assembly code in a DynASM source file. It must be
used exactly once in a DynASM source file, and must be the first directive in a DynASM source file.
Note that this documentation only covers .arch x86
and .arch x64
, though other
architectures have similar macros and directives. Where the two documented architectures differ,
this page marks architecture-specific behaviour with x86 or
x64.
| .byte imm8 [, imm8 [, ...]]
This data directive emits one or more unsigned 8-bit values.
| .capture name /* ... */ | .endcapture
This directive affects everything up until the next .endcapture
, and causes everything
that would be written to the output file to instead be appended to an internal buffer. The buffer
is identified by name, and multiple .capture
directives with the same
name will append to the same buffer. The internal buffer must, at some later point in
time, be written to the output file by means of .dumpcapture name
.
| .define name [, substitution]
This directive defines a prepreprocessor substitution. For the remainder of the DynASM source file,
on lines that start with a vertical bar, name
is treated as if it were
substitution
(or 1
if a substitution is not given).
| .dumpcapture name
This directive causes all output captured by prior .capture name
directives to be written to the output file.
| .dword imm32 [, imm32 [, ...]]
This data directive emits one or more unsigned 32-bit values.
| .elif condition
This prepreprocessor directive is to #elif
what .if
is to #if
.
| .else
This prepreprocessor directive is to #else
what .if
is to #if
.
| .endif
This prepreprocessor directive is to #endif
what .if
is to #if
.
| .error message
This prepreprocessor directive causes dynasm.lua to print message, continue processing the remainder of the DynASM source file, and then exit with a failure code.
| .externnames ident
This directive causes the dynasm.lua preprocessor to emit a piece of C code similar to:
static const char *const ident[] = { /* ... */ (const char *)0 };
The array defined by this directive contains the name of every extern address used in the DynASM source file. For example,
if a DynASM source file refers to (just) the extern addresses extern foo
and extern bar
, then
this directive would cause the following to be emitted:
static const char *const ident[] = { "foo", "bar", (const char *)0 };
Subsequently, when extern foo
is encoded, DASM_EXTERN
would be
invoked with index 0
, and then when extern bar
is encoded, it would be invoked with index 1
.
| .fatal message
This prepreprocessor directive causes dynasm.lua to print message, and then immediately exit with a failure code.
| .globalnames ident
This directive causes the dynasm.lua preprocessor to emit a piece of C code similar to:
static const char *const ident[] = { /* ... */ (const char *)0 };
The array defined by this directive contains the name of every ->
label used in the DynASM source file. For example,
if a DynASM source file refers to (just) the global labels ->foo
and ->bar
, then
this directive would cause the following to be emitted:
static const char *const ident[] = { "foo", "bar", (const char *)0 };
| .globals prefix
This directive causes the dynasm.lua preprocessor to emit a piece of C code similar to:
enum { /* ... */ prefix_MAX };
The enum defined by this directive implicitly maps every ->
label used in the DynASM source file to an
integer. For example, if a DynASM source file refers to (just) the global labels ->foo
and
->bar
, then this directive would cause the following to be emitted:
enum { prefixfoo, prefixbar, prefix_MAX };
This directive must be used exactly once in a DynASM source file, and the prefix_MAX
value it
defines must be passed as the third argument to dasm_setupglobal
.
The other values it defines can be used to obtain the address of the respective label by indexing the array passed as
the second argument to dasm_setupglobal
after dasm_encode
has been called.
| .if condition
If the given condition evaluates to 0
, false
, or nil
, then all lines from
the DynASM source file up until the next .elif
, .else
,
or .endif
directive are discarded. If the given condition evaluates to any other
value, said lines are kept.
The condition can be any Lua expression (and therefore use arithmetic, not
, and
, or
,
etc.), and can make reference to names defined with .define
, though it is evaluated
in an otherwise empty sandbox.
| .include filename
This prepreprocessor directive causes the contents of the named file (which should itself be a DynASM source file)
to be treated as if it appeared in place of the .include
directive. If filename is a relative
path, the current working directory will be searched for the file, followed by any include paths specified using
the -I
flag to dynasm.lua.
| .macro name [, param1 [, param2 [, ...]]] | /* ... */ | .endmacro
This directive causes name to be recognised as a macro for the remainder of the DynASM source file. Macros
are invoked as if they were normal assembly instructions, using the syntax name arg1, arg2, ...
.
The body of the macro can contain assembly instructions and DynASM directives on lines starting with one vertical bar,
and it can contain C/C++ code on lines starting with two vertical bars. Any such lines can contain parameter names,
which will be substituted for the provided arguments, and they can also contain ..
, which acts as a token
pasting operator.
Note that lines which do not start with a vertical bar are untouched by dynasm.lua, and this
remains true between .macro
and .endmacro
. As such, C/C++ code that is meant to be emitted
when the macro is invoked needs to be prefixed with two vertical bars rather than no vertical bars.
| .nop ...
This directive does nothing.
Note the difference between the directive .nop
, which does nothing, and the instruction nop
,
which is equivalent to .byte 0x90
.
| .sbyte imm8 [, imm8 [, ...]]
This data directive emits one or more signed 8-bit values.
| .section name1 [, name2 [, ...]]
This directive allows a DynASM source file to write multiple code segments rather than just a single code segment.
Every argument name
results in the introduction of a new directive .name
,
and using one of these introduced directives causes all subsequent assembly and data directives to append to the named
section. When dasm_encode
is called, the sections are concatenated to form a
single contiguous block of machine code.
This directive causes the dynasm.lua preprocessor to emit a piece of C code similar to:
#define DASM_SECTION_NAME1 0 #define DASM_SECTION_NAME2 1 /* ... */ #define DASM_MAXSECTION 2
This directive can be used at most once per DynASM source file, and if it is used, the DASM_MAXSECTION
macro that it defines should be passed to dasm_init
. The other macros that it
defines can be used with dasm_checkstep
.
| .space imm32 [, filler]
This data directive emits imm32 copies of the byte filler. If specified, filler must be an integer between 0 and 255, and defaults to zero if not specified.
| .type name, ctype [, default_reg]
This directive makes it easier to manipulate registers of type ctype*
by defining the
following syntactic sugar for the remainder of the DynASM source file:
Sugar | Expansion |
---|---|
#name | sizeof(ctype) |
name:reg->field | [reg + offsetof(ctype,field)] |
name:reg[imm32] | [reg + sizeof(ctype)*imm32] |
name:reg[imm32].field | [reg + sizeof(ctype)*imm32 + offsetof(ctype,field)] |
name:reg ... | [reg + (int)(ptrdiff_t)&(((ctype*)0) ... )] |
Note that the middle three forms of sugar are just special cases of the last form of sugar, and therefore there is no ambiguity between the last form and the earlier forms.
If default_reg
is specified, :reg
can be omitted from the sugar, and the expansion will use default_reg
.
| .word imm16 [, imm16 [, ...]]
This data directive emits one or more unsigned 16-bit values.