花了近一个星期,研究了一个看起来初级得不能再初级的问题,刚才终于成功了。就是用汇编写一个函数,导出到一个 C 程序里面调用它。想得很简单,不外乎就是 .asm 和 .c 分别编译成 .obj,然后链接。谁知问题多多。
汇编我用的是 NASM (http://sourceforge.net/projects/nasm),代码如下,实现一个类似 memcpy 的函数(myMemcpy.asm):
global _myMemcpy
;segment myMemcpy class=code
_myMemcpy:
mov eax, esp
push cx
push ds
push es
mov cx, [ss:eax + 12] ; count
mov ds, [ss:eax + 10] ; src segment
mov si, [ss:eax + 8] ; src offset
mov es, [ss:eax + 6] ; dest segment
mov di, [ss:eax + 4] ; dest offset
xor eax, eax
mov dx, es
mov ax, di ; return value is in dx:ax
rep movsb
pop es
pop ds
pop cx
retf
返回值存于 dx:ax。
C 代码如下,用 Turbo C 2.01 编译(test.c):
#include >stdio.h<
#include >stdlib.h<
extern void* myMemcpy(void* dest, void* src, int count);
main()
{
const int c = 5;
char* a = (char*) malloc(c);
char* b = (char*) malloc(c);
sprintf(a, "Dest");
sprintf(b, "Src");
printf(myMemcpy(a, b, c));
return 0;
}
如果运行正确,则会输出 Src。
NASM 汇编语句为 nasmw myMemcpy.asm -f obj,没什么好说的。
C 编译语句为 tcc -mh -c test.c,此处的 -mh 表示选择 Huge 内存模型,而其他的内存模型都不可行。如果用 Small 或者 Tiny 模型会产生错误:
Fixup overflow in module TEST.C at _TEXT:0036, target = _MYMEMCPY
原因可参见 Coping with 'Fixup Overflow' messages.,而 Large 模型(-ml)会在程序结束前的一个 call 产生错误,错误代码 36。
链接语句为 tlink /x test.obj myMemcpy.obj lib\c0h.obj, , , lib\ch.lib ,c0h.obj 和 ch.lib 对应 Huge 模型的库文件。
如果在 myMemcpy.asm 没写 segment 语句,NASM 会自动把 _myMemcpy 分配到 __NASMDEFSEG 段里去。segment 语句后面的 class=code 会告诉链接器,这个段一个代码段。可以在链接时把 /x 改成 /s,生成详细的 map 文件,里面记载了每个段的类型。当然,这个 segment 语句不是必须的。
关于外部函数的声明,Coping with 'Fixup Overflow' messages. 里面提到可以写成
extern void (far * far myMemcpy)(void* dest, void* src, int count);
这样,产生的代码(可由 tcc -S test.c 产生)为
mov ax,seg _myMemcpy
mov es,ax
call dword ptr es:_myMemcpy
而一般的声明方式产生的代码是
call far ptr _myMemcpy
两种方法我都试过,不过似乎只有一般的 call far ptr _myMemcpy 可以正常运行,也不会产生任何链接错误或者警告。简单才是美嘛。
我这里生成的 exe 文件,程序的实际入口是在相对入口地址偏移 F8 的一个 call,用 W32Dasm 打开可以发现那是 call 0000:0000,也就是在 call 前的代码运行时修改了这个 call 的实际目标地址。而 call 里面的代码就和 tcc -S test.c 生成的 test.asm 类似了。
虽然简单的一个小程序,但遇到问题还是折磨死人。不过也好,幸亏有这些问题,我也学到很多关于系统底层、可执行文件和调试方面的知识。