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_linkdasm_encodedasm_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 的第一个参数。

在进入时,psz 参数将匹配来自先前调用 DASM_M_GROW 的退出的 psz 参数,即 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_checkstepdasm_growpcdasm_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_checkstepdasm_link的后续调用将返回一个非零值。

DASM_FDEF int dasm_link(Dst_DECL, size_t *szp);

这个函数在dasm_encode之前被调用,用来计算将要生成的机器代码的大小。API的消费者通常会调用dasm_link,分配适当大小的内存,然后调用在dasm_encode

如果定义了DASM_CHECKS,并且之前对dasm_putdasm_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.