最近在为distri.lua实现一个lua调试系统,有一个简单的需求,lua导入一个文件的时候,将这个文件的文件名记录下来,

以方便调试器在设置断点的时候判断是否一个合法的文件.

lua导入文件是通过luaL_loadfilex实现的,一个简单的思路就是修改luaL_loadfilex,在luaL_loadfilex中调用一个外部定义的函数将导入的文件名传给那个外部函数,由它记录下来.

但这种侵入式的方案,除非在逼不得已的情况下不应该使用.

另一个思路是hook luaL_loadfilex,在运行时用另外一个函数替换luaL_loadfilex,由这个替换函数去记录下需要的信息

然后在跳转回原luaL_loadfilex的执行流程上.

与是我从decode中提取除了Hook.h,Hook.c稍加调整以适应Linux系统.

hook的原理很简单:

    * 首先,使用mprotect将luaL_loadfilex所在代码段设置为可读/可写/可执行,以避免在修改代码时出现段访问异常

    * 之后需要把luaL_loadfilex最前面的一段指令替换成一个跳转指令,跳转到替换函数中去执行.为了在替换函数执行
完之后可以正确的回到luaL_loadfilex的正常执行路径上,需要把luaL_loadfilex中被替换部分的指令保存下来,然后
在后面再添加一条跳转指令,调到luaL_loadfilex后面的执行路径去.

被替换掉的指令布局和执行流程如下图:

    luaL_loadfilex:
jmp hook
-------------
其余指令 <-------------------------------------------------|
|
hook: |
执行必要的记录 |
--保存的代码---- |
luaL_loadfilex中被替换的指令 |
jmp其余指令--------------------------------------------------|

HookFunction实现如下:

    void* HookFunction(void* function, void* hook)
{ // Don't allow rehooking of the same function since that screws things up. assert(!GetIsHooked(function, hook)); if (GetIsHooked(function, hook))
{
return NULL;
} // Missing from windows.h
//#define HEAP_CREATE_ENABLE_EXECUTE 0x00040000 // Jump instruction is 5 bytes.
const int jumpSize = 5; // Compute the instruction boundary so we don't override half an instruction.
int boundary = GetInstructionBoundary(function, jumpSize); size_t pagesize = sysconf(_SC_PAGE_SIZE);
unsigned char* trampoline = NULL;
trampoline = (unsigned char*)/*aligned_alloc*/memalign(pagesize,pagesize);
if(mprotect(trampoline, pagesize, PROT_WRITE|PROT_READ|PROT_EXEC)){
free(trampoline);
return NULL;
} // Copy the original bytes to the trampoline area and append a jump back
// to the original function (after our jump). memcpy(trampoline, function, boundary);
AdjustRelativeJumps(trampoline, boundary, ((unsigned char*)function) - trampoline); WriteJump(trampoline + boundary, ((unsigned char*)function) + boundary); void *ptr = (void*)(((size_t)function/pagesize)*pagesize);
// Give ourself write access to the region.
if(!mprotect(ptr, pagesize, PROT_WRITE|PROT_READ|PROT_EXEC))
{
// Fill the area with nops and write the jump instruction to our
// hook function.
memset(function, 0x90, boundary);
WriteJump(function, hook); // Restore the original protections.
//VirtualProtect(function, boundary, protection, &protection);
mprotect(ptr, pagesize, PROT_READ|PROT_EXEC);
// Flush the cache so we know that our new code gets executed.
//FlushInstructionCache(GetCurrentProcess(), NULL, NULL); return trampoline;
//return 0; } free(trampoline);
return NULL; //return -1;
}

本以为一切就这样结束了,运行程序的时候,正确的进入了替换函数,但在执行完记录操作要回到luaL_loadfilex后续执行流程的时候程序

挂了,报段访问异常.

为啥呢,我们看下WriteJump的实现:

/**
* Writes a relative jmp instruction.
*/
void WriteJump(void* dst, void* address)
{ unsigned char* jump = (unsigned char*)(dst); // Setup a jump instruction.
jump[0] = 0xE9;
*((unsigned long*)(&jump[1])) = (unsigned long)(address) - (unsigned long)(dst) - 5; }

使用的是E9跳转指令+4字节的立即数做相对rip计数器的跳转.这在32位程序下这是没问题的,因为trampoline和luaL_loadfilex的位移差必定

在4字节的范围内.但我程序的运行环境是64位的,这个时候程序就出问题了, trampoline和luaL_loadfilex的位移差已经超过4个字节.这就导致

[jmp其余指令]跳转到到错误的地址上了.

如何解决这个问题:

*   用FF指令做跳转,但这个方案要修改的地方就多了,除了` WriteJump`,还有`AdjustRelativeJumps`并且还会导致指令长度变长.

*   用static数据区保存luaL_loadfilex中被替换的指令,使得位移差被控制在4字节以内.

针对我的需求,我选择了方案2,下面是修改过的HookFunction和WriteJump以及使用示例:

    /**
* Writes a relative jmp instruction.
*/
void WriteJump(void* dst, void* address)
{ unsigned char* jump = (unsigned char*)(dst); // Setup a jump instruction.
jump[0] = 0xE9;
*((unsigned int*)(&jump[1])) = (unsigned int)((unsigned long)(address) - (unsigned long)(dst) - 5); } void* HookFunction(void* function, void* hook,void *saveaddr,size_t saveaddr_size)
{ // Don't allow rehooking of the same function since that screws things up. assert(!GetIsHooked(function, hook)); if (GetIsHooked(function, hook))
{
return NULL;
} // Missing from windows.h
//#define HEAP_CREATE_ENABLE_EXECUTE 0x00040000 // Jump instruction is 5 bytes.
const int jumpSize = 5; // Compute the instruction boundary so we don't override half an instruction.
int boundary = GetInstructionBoundary(function, jumpSize); if(saveaddr_size < (size_t)boundary) return NULL; size_t pagesize = sysconf(_SC_PAGE_SIZE);
if(mprotect(saveaddr, boundary, PROT_WRITE|PROT_READ|PROT_EXEC)){
//free(trampoline);
return NULL;
} // Copy the original bytes to the trampoline area and append a jump back
// to the original function (after our jump). memcpy(saveaddr, function, boundary);
AdjustRelativeJumps(saveaddr, boundary, ((unsigned char*)function) - (unsigned char*)saveaddr); WriteJump(saveaddr + boundary, ((unsigned char*)function) + boundary); void *ptr = (void*)(((size_t)function/pagesize)*pagesize);
// Give ourself write access to the region.
if(!mprotect(ptr, pagesize, PROT_WRITE|PROT_READ|PROT_EXEC))
{
// Fill the area with nops and write the jump instruction to our
// hook function.
memset(function, 0x90, boundary);
WriteJump(function, hook); // Restore the original protections.
//VirtualProtect(function, boundary, protection, &protection);
mprotect(ptr, pagesize, PROT_READ|PROT_EXEC);
// Flush the cache so we know that our new code gets executed.
//FlushInstructionCache(GetCurrentProcess(), NULL, NULL); return saveaddr;
//return 0; } return NULL; //return -1; }

////使用示例

    int (*ori_luaL_loadfilex)(lua_State *L, const char *filename,const char *mode) = NULL;

    int my_luaL_loadfilex(lua_State *L, const char *filename,const char *mode){
printf("%s\n",filename);//记录导入的lua文件,供调试器使用
return ori_luaL_loadfilex(L,filename,mode);
} static char luaL_loadfilex_buf[4096] __attribute__((aligned(4096))); int debug_init(){
ori_luaL_loadfilex = HookFunction(luaL_loadfilex,my_luaL_loadfilex,luaL_loadfilex_buf,4096);
if(!ori_luaL_loadfilex){
return -1;
}
return 0;
}

Hook lua库函数时遇到的问题的更多相关文章

  1. [lua大坑]一个莫名其妙的lua执行时崩溃引出的堆栈大小问题

    这是一个坑,天坑!如果不是我随手删除了一个本地变量,这个问题直到现在我应该也没有头绪. 首先,写了一个新的lua脚本,载入,执行.在执行的时候,出了这么一个莫名其妙的问题: EXC_BAD_ACCES ...

  2. lua库函数

    这些函数都是Lua编程语言的一部分, 点击这里了解更多. assert(value) - 检查一个值是否为非nil, 若不是则(如果在wow.exe打开调试命令)显示对话框以及输出错误调试信息 col ...

  3. 【原创】lua编译时发现缺少readline库

    编译lualua项目,其中用到了lua-5.1版本的源码,编译时提示缺少readline库,找不到readline/readline.h头文件等 发现系统中其实有安装readline库不过没有做链接和 ...

  4. 写lua时需要注意的地方

    条件语句判断时,只有false和nil会导致判断为假,其他的任何值都为真. Lua 的字符串与编码无关: 它不关心字符串中具体内容. 标准 Lua 使用 64 位整数和双精度(64 位)浮点数, 但你 ...

  5. mac 升级EI Capitan后遇到c++转lua时遇到libclang.dylib找不到的错

    升级EI Capitan后,打包lua脚本时,会报这个错: LibclangError: dlopen(libclang.dylib, 6): image not found. To provide ...

  6. 安装nginx环境(含lua)时遇到报错ngx_http_lua_common.h:20:20: error: luajit.h: No such file or directory的解决

    下面是安装nginx+lua环境时使用的相关模块及版本,ngx_devel_kit和lua-nginx-module模块用的都是github上最新的模块.并进行了LuaJIT的安装. #Install ...

  7. Lua 5.1 参考手册

    Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de Figueiredo, Waldemar Celes 云风 译 www.codingno ...

  8. Lua 5.3 参考手册

    转自:http://www.runoob.com/manual/lua53doc/manual.html 1 – 简介 Lua 是一门扩展式程序设计语言,被设计成支持通用过程式编程,并有相关数据描述设 ...

  9. lua 5.3 英文手册 google机器翻译版

    LUA Lua 5.3参考手册作者:Roberto Ierusalimschy,Luiz Henrique de Figueiredo,Waldemar Celes 版权所有©2015-2018 Lu ...

随机推荐

  1. 我使用过的Linux命令之date - 显示、修改系统日期时间(转)

    用途说明 ate命令可以用来显示和修改系统日期时间,注意不是time命令. 常用参数 格式:date 显示当前日期时间. 格式:date mmddHHMM 格式:date mmddHHMMYYYY 格 ...

  2. C语言事实上不简单:sizeof

    问:C语言中一共同拥有多少个keyword? 答:32个. 答不上来的没关系.非常正常.我们玩的是程序的艺术.而不是背数字. 只是这个特殊的数字1<<5也是非常好记的-.-. 问:size ...

  3. Jenkins和maven自动化构建java程序

    转自:http://www.cnblogs.com/gao241/archive/2013/04/08/3008380.html,版权归原作者所有. Jenkins是一个非常出色的持续集成服务器,本文 ...

  4. Android四大组件应用系列5——使用AIDL实现跨进程调用Service

    一.问题描述 Android应用程序的四大组件中Activity.BroadcastReceiver.ContentProvider.Service都可以进行跨进程.在上一篇我们通过ContentPr ...

  5. web中ajax跨域与同源文章 from 阮一峰

    跨域资源共享 CORS 详解 http://www.ruanyifeng.com/blog/2016/04/cors.html  //这个是最明白的..建议看看. http://www.ruanyif ...

  6. 这些年我在技术路上做过最虚伪愚蠢的事情,就是在CSDN上刷屏赚分

    现在似乎Github成了所谓技术人士的新宠,之前是博客,更早则是论坛. CSDN是众多技术论坛里比较突出的一个,人多高手也多,很多问题都能得到满意的回答. 谁都希望自己卓尔不群,我也不例外,我也想像那 ...

  7. Linux中使用sendmail发送邮件,指定任意邮件发送人

    一.使用任意发件人发送邮件 echo .com -s .com 其中s表示主题.

  8. redis 基本信息查询

    在客户端可以用telnet命令 telnet ip port 再输入info 返回如下信息:

  9. 12C -- ORA-01033: ORACLE initialization or shutdown in progress

    初装oracle 12.2 rac数据库. 登录RAC数据库中第1节点 $ sqlplus '/as sysdba' SQL> select name,open_mode from v$pdbs ...

  10. kubernetes中port、target port、node port的对比分析,以及kube-proxy代理

    转:http://blog.csdn.net/xinghun_4/article/details/50492041 容器网络实例 服务中的3个端口设置 这几个port的概念很容易混淆,比如创建如下se ...