[转]将tcc 载入工程 编译脚本

来源:互联网 发布:php 统计图 编辑:程序博客网 时间:2024/05/24 05:43

原文地址:http://blog.csdn.net/nondeep/article/details/8498430

tiny cc 编译器

tcc是一个支持windows和linux环境的C语言编译器。它也可以将代码编译之后直接嵌入到进程中,这就具有脚本的相关特性了,但它却不是解释执行的。

 tcc将C源码编译称机器码,然后分配一块内存,将机器码写在这块内存中,宿主进程可以调用源码中所定义的函数,源码也可以调用宿主进程提供的函数,交互还是相当灵活的。

 我们可以将tcc源码编译称DLL(就直接用TCC自己编译自己,网上有具体的方法),这里只简单介绍一下如何将tcc.dll加入到我们的工程中,以此为我们的工程增加脚本功能,步骤如下:

 

包含头文件,导入库文件,这就不多说了

 1.创建编译环境(包括状态、符号表等等的初始化)
TCCState *s=::tcc_new();
if(s==0)
{
 printf("can not create compile environment\n"); return 0;
}

2.设置错误处理回调函数
::tcc_set_error_func(s,NULL,call_back_func_addr);

3.设置头文件和库文件目录
::tcc_add_sysinclude_path(s,"tcc/include");
::tcc_add_sysinclude_path(s,"tcc/include/winapi");
::tcc_add_sysinclude_path(s,"tcc/include/sys");

::tcc_add_library_path(s,"tcc/lib");

4.添加支持库(最好不要给脚本太多的API支持,如果用户要干坏事的话,太多的API支持让挂接的工作都省了。这也是C语言不适合做脚本的原因,因为他离操作系统太近了。起码也得在宿主进程和脚本之间隔一层虚拟机之类的东西,才能算得上有些安全。)
::tcc_add_library(hTcc,"user32");
::tcc_add_library(hTcc,"gdi32");
//::tcc_add_library(hTcc,"kernel32");

5.添加函数给脚本调用(也可以添加变量)
int arg_extern_test=123456;
int (*api_malloc)(int); api_malloc=malloc;
::tcc_add_symbol(s,"api_malloc",api_malloc);
::tcc_add_symbol(s,"arg_extern_test",(void*)&arg_extern_test);

5.设置机器码的存储类型(是生成一个硬盘EXE文件还是直接放到进程空间)
::tcc_set_output_type(s,TCC_OUTPUT_MEMORY);

6.编译(编译生成的机器码暂时还存放在语法树中,所以这时候只能确定指令的大小而不能确定指令起始地址)
if(::tcc_compile_string(s,g_source_string)==-1)
{
 printf("compile failed!\n"); return 0;
}

7.获取机器码的总字节数
int size=::tcc_relocate(s,NULL);
if(size==-1)
{
 printf("can not get opcode size\n"); return 0;
}

8.分配内存,存储机器码,存储结束后才能从符号表中搜索符号对应的地址
//从链表中取出机器码存放在opcode指向的内存块中(此过程中还会修改jmp,call这类指令的偏移以确保正确)
void *opcode=::malloc(size); ::tcc_relocate(s,opcode);

9.从符号表中查找脚本所定义的函数地址(这是让宿主进程能够实时地调用脚本中定义的函数)
void (*msg_init)();
void (*msg_exit)();
msg_init=(void(*)())::tcc_get_symbol(s,"msg_init");
msg_exit=(void(*)())::tcc_get_symbol(s,"msg_exit");

10.删除编译环境(删除包括编译过程中产生的临时数据,例如符号表、类型表、语法树等等)
::tcc_delete(s);

说明: 其实7、8两步完全可以由tcc内部处理的,可能是为了灵活性,最终还是采用了这种有些别扭的方式

 

主进程可以把任何事件交给脚本处理,比如点下某个按钮的时候,主进程就在脚本中找int msg_button_down,找到了就调用,脚本处理完后返回一个结果给主进程,本人习惯把这一过程称之为消息响应。当然,主进程必须提供足够多的函数,本人也习惯把这种功能函数称作接口。

 

以前用它写过某贴吧发贴机,效果如图:

 

 
tcc貌似还有个小问题(当然,这个所谓的问题可能是由于我用的不够精的缘故),本人发现,tcc貌似不认识__stdcall童鞋,脚本中出现任何调用约定,立马报错。这问题貌似以前遇到过,后来好像找到了解决办法,隔一段时间再用,好像又忘记了。
 
——————————————————————————————————————
 
又去看了一遍libtcc.c,终于想起来了,tcc的确不能直接识别__stdcall和__cdecl(其他约定没有测试),平时写习惯了,也不知道__stdcall__更纯粹还是windows经常使用的__stdcall才是正宗,反正要想在TCC脚本中使用那些我们习惯的调用约定,只要定义一个类似这样的宏:
#define __cdecl __attribute__((__cdecl__))
#define __stdcall __attribute__((__stdcall__))
或者,直接就用__cdecl__,反正也就是多两条下划线的事:
 

----------------------------------------------------------------------------------------------------------------------
//tcc 内联汇编
void *my_memcpy(void * to, const void * from, int n)
{
int d0, d1, d2;
__asm__ __volatile__(
        "rep ; movsl\n\t"
        "testb $2,%b4\n\t"
        "je 1f\n\t"
        "movsw\n"
        "1:\ttestb $1,%b4\n\t"
        "je 2f\n\t"
        "movsb\n"
        "2:"
        : "=&c" (d0), "=&D" (d1), "=&S" (d2)
        :"0" (n/4), "q" (n),"1" ((long) to),"2" ((long) from)
        : "memory");
return (to);
}

 

0 0
原创粉丝点击