Dynasm 整理
DynASM Macros(宏定义)
可以在编译时定义一系列宏来修改 DynASM 的行为。 如果特定宏的默认定义不合适,则应在包含任何 DynASM 标头之前重新定义它。
DASM_ALIGNED_WRITES
定义了DASM_ALIGNED_WRITES(任何值)的时候,如果写入目标可能未对齐,dasm_x86.h 将使用多字节粒度写入而不是单个多字节写入。
这应该在 x86/x64 以外的主机平台上发出 x86/x64 机器代码时定义。
DASM_CHECKS
定义了DASM_CHECKS(任何值)的时候,,dasm_x86.h 中的 dasm_link、dasm_encode 和 dasm_checkstep 的实现将执行额外的健全性检查,以确保正确使用 DynASM。
如果关注编码速度,可能需要在开发和调试构建期间定义 DASM_CHECKS,否则取消定义。
DASM_EXTERN
#ifndef DASM_EXTERN
#define DASM_EXTERN(ctx, addr, idx, type) 0
#endif
当 dasm_encode 需要对外部地址进行编码时,dasm_x86.h 会使用用户自定义的。并且,如果使用了外部地址,则必须重新定义DASM_EXTERN。
ctx 参数与 Dst_DECL 定义的变量类型相同,并将设置为 dasm_encode 的第一个参数。
addr 参数的类型为 unsigned char*,并给出一个 int32_t 字段的地址,该字段将作为 x86/x64 指令的一部分写入。
idx 参数的类型为 unsigned int,并给.externnames 数组的索引,其中标识了正在写入的特定外部地址。
type 参数不为零,表示正在写入的 int32_t 字段表示从正在编码的指令末尾的带符号的 32 位相对偏移量(即相对于 addr+4)。零表示绝对地址 x86,或在低 2GB 地址空间 x64 中的绝对地址。
宏应计算为 32 位整数值,随后将写入 (int32_t*)addr。
例如,如果外部名称是可动态查找的符号,则可以将其定义为:
#define DASM_EXTERN(ctx, addr, idx, type) (\
(type) ? (int)((unsigned char*)dlsym(RTLD_DEFAULT, externs[idx]) - (addr) - 4) \
: (int)dlsym(RTLD_DEFAULT, externs[idx]))
DASM_FDEF
#ifndef DASM_FDEF
#define DASM_FDEF extern
#endif
此用户定义的宏用于控制所有 dasm_ 函数上的说明符。 例如,在共享库中嵌入 DynASM 的 Windows 应用程序可能会将其重新定义为 __declspec(dllimport) 或 __declspec(dllexport)。 相反,只在一个地方使用 DynASM 并且不想污染符号命名空间的 Linux 应用程序可能会将其重新定义为 attribute((visibility(“hidden”))) 或只是static。
DASM_M_FREE
#ifndef DASM_M_FREE
#define DASM_M_FREE(ctx, p, sz) free(p)
#endif
dasm_x86.h 使用这个用户定义的宏来实现 dasm_free 和释放之前由 DASM_M_GROW 分配的内存。
ctx 参数与Dst_DECL 定义的变量具有相同的类型,并将设置为 dasm_free 的第一个参数。
在进入时,p 和 sz 参数将匹配来自先前调用 DASM_M_GROW 的退出的 p 和 sz 参数,即 p 给出一个指向需要释放的内存的指针,并且 sz 给出之前在 p 处分配的字节数 .
DASM_M_GROW
#ifndef DASM_M_GROW
#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)
#endif
每当需要分配或重新分配内存时,dasm_x86.h 都会使用这个用户定义的宏——dasm_init 总是需要,而其他各种 dasm_ 函数有时也需要。 当默认的 C realloc/free 分配机制不合适时,它应该与 DASM_M_FREE 一起重新定义。
ctx 参数与 Dst_DECL 定义的变量具有相同的类型,并将设置为执行分配的 dasm_ 函数的第一个参数。
在输入时,p 参数给出一个 t* 类型的左值,大小为 sz 字节。 特别注意,sz 可以为零表示应该执行分配操作(在这种情况下 p 将为 NULL),或者非零表示应该执行重新分配操作(在这种情况下 p 不会 无效的)。
退出时,p 参数应该给出一个 t* 类型的左值,它的大小至少 need 字节,而 sz 参数应该给出一个包含实际字节数的左值。
Dst_DECL
#ifndef Dst_DECL
#define Dst_DECL dasm_State **Dst
#endif
这个用户定义的宏影响所有dasm_ 函数的签名,特别是给出第一个形参的类型和名称。所述参数的名称必须保持为Dst,或者必须有一个预处理器宏重新定义Dst以匹配这里使用的名称。所述参数的类型可以是允许Dst_REF给出dasm_State*类型的左值的任何类型。
按照惯例,具有与dasm_State相关联的状态集合的DynASM的非平凡用户将定义包含dasm_State和相关状态的结构,并将重新定义Dst_DECL为指向所述结构的指针。
例如,在C++项目中使用DynASM时,可以使用以下定义来确保RAII语义,并将其他字段传递给发射器函数:
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
Dst_REF
#ifndef Dst_REF
#define Dst_REF (*Dst)
#endif
dasm_x86.h使用这个用户定义的宏从名为Dst的变量中提取dasm_State*类型的左值。当且仅当Dst_DECL时也被重新定义时,它才应该被重新定义。
DynASM API
DynASM的API由10个函数组成,他们的调用必须严格按照一下顺序:
顺序 | 可调用函数 |
---|---|
1 | dasm_init |
2 | dasm_setupglobal |
3 | dasm_setup |
4 | dasm_checkstep、dasm_growpc、dasm_put中的任何一个,每次0次或多次 |
5 | dasm_link |
6 | dasm_encode |
7 | dasm_getpclabel, 0次或多次 |
8 | dasm_free |
dasm_init
DASM_FDEF void dasm_init(Dst_DECL, int maxsection);
该函数执行 DynASM 状态初始化的第一阶段。 可以通过重新定义 DASM_M_GROW 来修改其行为。
如果使用 .section指令,则 DASM_MAXSECTION (DASM_MAXSECTION是定义的section数量,在生成的代码中宏定义) 应作为 maxsection 传递。 否则,应传递值 1。
调用此函数之后必须调用 dasm_setupglobal 以执行初始化的第二阶段,并且稍后应调用 dasm_free 以释放 DynASM 状态。
dasm_setupglobal
DASM_FDEF void dasm_setupglobal(Dst_DECL, void **gl, unsigned int maxgl);
此函数必须在dasm_init之后、dasm_setup之前调用,以执行DynASM状态初始化的第二阶段。
参数maxgl必须传入全局枚举类型中值的个数(全局枚举类型是由 .globals 指令定义的)
参数 gl 则必须传入根据指向全局枚举类型生成的数组。
已全局枚举类型ident为例,ident_MAX就是类型值的个数,globals就是对应的数组
void* globals[ident_MAX];
dasm_setupglobal(&d, globals, ident_MAX);
在调用dasm_encode之前,该数组用作暂存空间,然后数组的元素给出->标签的地址。继续前面的例子,全局枚举类型的变量bar的地址将由globals[ident_bar]给出。
(->标签用于定义枚举类型的值,换成C理解就是在枚举类型ident设变量bar=1。
enum ident {
bar = 1
};
具体定义看.globals以及Global_Labels)
dasm_setup
DASM_FDEF void dasm_setup(Dst_DECL, const void *actionlist);
必须在 dasm_setupglobal 之后调用此函数,以完成DynASM状态的初始化。
由定义的数组.actionlist必须作为actionlist参数传递,并将由对dasm_put的后续调用使用。
dasm_checkstep
DASM_FDEF int dasm_checkstep(Dst_DECL, int secmatch); *actionlist);
在一系列对dasm_put的调用之后,可以调用这个函数来执行额外的健全性检查。
如果定义了了DASM_CHECKS,并且之前对dasm_put的调用遇到了错误,则该函数将返回一个非零结果来指示错误。
如果定义了DASM_CHECKS,并且标签1:到9:中的任何一个已被引用但未定义,此函数将返回DASM_S_UNDEF_L | i,其中i是有问题的标签号。如果标签没有问题,那么这个函数将取消定义标签1:到9:。
如果定义了DASM_CHECKS,则secmatch参数为非负,并且当前正在写入的secion的索引(请参见.section)不等于secmatch,则该函数将返回DASM_S_MATCH_SEC | i,其中i是当前section的索引。
否则,该函数将返回值0以表示成功。
dasm_growpc
DASM_FDEF void dasm_growpc(Dst_DECL, unsigned int maxpc);
可以在dasm_setup之后和dasm_link之前调用此函数,以增加可用于=>pc语法的标签数量。特别是,在调用这个函数之后,可以使用标签=>0到=>(maxpc-1)。
dasm_put
DASM_FDEF void dasm_put(Dst_DECL, int start, ...);
DynASM源文件中以一个竖线开始的行被dynasm.lua预处理程序转换为对dasm_put的调用。从概念上讲,对这个函数的调用是将一些汇编代码附加到动态状态,dasm_link / dasm_encode稍后会将这些代码转换成机器码。
对这个函数的调用不应该由人类编写;它们应该只由dynasm.lua预处理程序编写。也就是说,start参数在传递给dasm_setup的动作列表中给出了一个偏移量,variadic参数用于传递原始汇编代码中编译时期常数的具体值。
如果定义了DASM_CHECKS,该函数将执行额外的健全性检查。如果这些检查中的任何一个失败,对dasm_checkstep和dasm_link的后续调用将返回一个非零值。
dasm_link
DASM_FDEF int dasm_link(Dst_DECL, size_t *szp);
这个函数在dasm_encode之前被调用,用来计算将要生成的机器代码的大小。API的消费者通常会调用dasm_link,分配适当大小的内存,然后调用在dasm_encode。
如果定义了DASM_CHECKS,并且之前对dasm_put或dasm_checkstep的调用遇到了错误,则该函数将返回一个非零结果来指示错误。
如果定义了DASM_CHECKS,并且使用了标签=>pc但未定义,此函数将返回DASM_S_UNDEF_PC | pc。在所有其他情况下,该函数将返回值0以表示成功。
dasm_encode
DASM_FDEF int dasm_encode(Dst_DECL, void *buffer);
这个函数在dasm_link之后被调用,以便实际生成机器码。
在进入时,buffer参数应该指向一个内存块,其字节大小至少是dasm_link返回的大小。在调用dasm_encode期间,这个内存块必须至少是可读和可写的。
在退出时,buff的内存块将被机器代码填充,并且传递给dasm_setupglobal的全局数组将被指针填充到所述块中。如果要执行机器代码(而不仅仅是写入磁盘),那么在dasm_encode之后,内存块至少需要是可读和可执行的。诸如VirtualProtect (Windows)或mprotect (POSIX)之类的操作系统API可能适合于将内存标记为可执行的。
这个函数应该总是返回0,但是如果触发了DynASM错误,它可以返回非零值。
dasm_getpclabel
DASM_FDEF int dasm_getpclabel(Dst_DECL, unsigned int pc);
可以在dasm_encode之后调用此函数,以便检索标签=>pc:的地址。
如果成功,返回值是从传递给dasm_encode的缓冲区开始到label =>pc:的非负偏移量。
出错时(例如=>pc:未定义),返回值为负。
dasm_free
DASM_FDEF void dasm_free(Dst_DECL);
这个函数释放以前由dasm_init分配的DynASM state。它的行为可以通过重新定义DASM_M_FREE来改变。
翻译来源
https://karminski.github.io/dynasm-doc/reference.html#dasm_checkstep
Q.E.D.