在源码查看过程中,涉及到dll 和 so 文件的导入和导出,此时就必须要了解各种函数的跳转过程和导入导出过程,也就必须要彻底了解 .spec 文件。其中尤其ntdll.spec 和user32.spec 为甚,涉及非常多的常用函数接口。
关于spec 文件的详细注释查阅官方文档 https://www.winehq.org/docs/winebuild
通用语法
spec规范文件应该包含一个有序声明的列表。通用语法如下:
ordinal functype [flags] exportname ( [args...] ) [handler]
ordinal variable [flags] exportname ( [data...] )
ordinal extern [flags] exportname [symbolname]
ordinal stub [flags] exportname [ (args...) ]
ordinal equate [flags] exportname data
# comments
声明必须放在一行上,除非在行的末尾使用反斜杠字符转义。一行中任何位置的#字符都会导致该行的剩余部分作为注释被忽略。
序数(ordinal )指定与入口点相对应的序数,或“@”用于自动序数分配(仅Win32)。
flags是一系列可选的标志,前面有一个“-”字符。支持的标志如下:
-norelay
中继调试跟踪中(WINEDEBUG=+relay 时)不显示入口点(仅Win32)。
-noname
入口点将按序号而不是按名称导出。该名称仍然可用于导入。
-ret16
该函数返回一个16位值(仅限Win16)。
-ret64
该函数返回一个64位值(仅限Win32)。
-register
函数使用CPU寄存器来传递参数。
-private
该函数无法从其他dll导入,只能通过GetProcAddress访问。
-ordinal
入口点将按序号而不是按名称导入。该名称仍将被导出。
-thiscall
该函数使用thiscall调用约定(i386上%ecx寄存器中的第一个参数)。
-fastcall
该函数使用快速调用约定(i386上%ecx/%edx寄存器中的前两个参数)。
-syscall
函数是一个NT系统调用。将生成一个系统调用thunk,实际函数将由在Unix库端生成的__wine_syscall_dispatcher函数调用。
-import
该函数是从另一个模块导入的。当应用程序希望在dll中找到函数的实现时,可以使用它来代替转发规则。
-arch=[!]cpu[,cpu]
入口点仅在指定的CPU体系结构上可用。名称win32和win64分别与所有32位或64位CPU体系结构相匹配。在16位dll中,指定-arch=win32会导致从32位包装器模块导出入口点。CPU名称可以加前缀!以仅排除该特定架构。
函数规则
语法:
ordinal functype [flags] exportname ( [args...] ) [handler]
此声明定义了一个函数入口点。exportname([args…])定义的原型指定了可用于动态链接的名称和参数的格式。对于仅用于序号的导出,可以使用“@” 替代 exportname。
-
functype应为以下类型之一:
stdcall
正常Win32 函数
pascal
正常Win16 函数
cdecl
使用C调用约定的Win16或Win32函数
varargs
Win16或Win32函数,使用具有可变参数数的C调用约定 -
args应该是以下中的一个或多个:
word
(16-bit unsigned value)
s_word
(16-bit signed word)
long
(pointer-sized integer value)
int64
(64-bit integer value)
int128
(128-bit integer value)
float
(32-bit floating point value)
double
(64-bit floating point value)
ptr
(linear pointer)
str
(linear pointer to a null-terminated ASCII string)
wstr
(linear pointer to a null-terminated Unicode string)
segptr
(segmented pointer)
segstr
(segmented pointer to a null-terminated ASCII string).
注:16位和分段指针类型仅对Win16函数有效。
- 处理函数
是将在32位模式下实现该入口点的实际C函数的名称。处理程序也可以指定为dllname.function来定义转发函数(其实现在另一个dll中)。如果未指定处理程序,则假定它与exportname相同。
第一个例子定义了32位GetFocus() 调用的入口点:
@ stdcall GetFocus() GetFocus
第二个例子定义了16位CreateWindow()调用的入口点(序号100只是一个例子);它还显示了使用反斜杠可以拆分较长的行:
100 pascal CreateWindow(ptr ptr long s_word s_word s_word \
s_word word word word ptr) WIN_CreateWindow
若要使用可变数量的参数声明函数,请将该函数指定为varargs,并在C文件中用“…”声明Win32函数的参数,或Win16函数的额外VA_LIST16参数。有关示例,请参阅user32.spec中的wsprintf*函数如下:
@ varargs wsprintfA(str str)
@ varargs wsprintfW(wstr wstr)
Variable ordinals
语法:
ordinal variable [flags] exportname ( [data...] )
此声明将数据存储定义为指定序号的32位整形数据中。exportname将是可用于动态链接的名称。data 可以是十进制数,也可以是前面有“0x”的十六进制数。以下示例将变量VariableA定义为序号2,包含4个整数:
2 variable VariableA(-1 0xff 0 0)
此声明仅适用于Win16规范文件。在Win32中,您应该使用extern(请参阅下文)。
Extern ordinals
语法:
ordinal stub [flags] exportname [ (args...) ]
这个声明定义了一个存根函数。它使名称和序号可用于动态链接,但如果调用函数,则会终止执行并显示错误消息。
Equate ordinals
语法:
ordinal equate [flags] exportname data
此声明将序数定义为绝对值。exportname将是可用于动态链接的名称。数据可以是十进制数,也可以是前面有“0x”的十六进制数。
Api sets
语法:
apiset apiset_dll = target.dll [host.dll:target.dll]
此声明定义了apiset_dll(形式为api ms-*)解析为目标dll。可以选择指定其他目标以针对特定主机dll进行不同的解析。例如:
api-ms-win-core-processenvironment-l1-1-0 = kernelbase.dll
api-ms-win-core-processthreads-l1-1-0 = kernel32.dll \
kernel32.dll:kernelbase.dll
如果定义了apiset,则将在PE二进制文件中生成相应的.apiset部分。这需要使用–data-only选项构建模块。