最近在为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. The op amp module

  2. Java知识回顾 (6)正则表达式、方法、文件与异常

    一.正则表达式 正则表达式定义了字符串的模式. 正则表达式可以用来搜索.编辑或处理文本. 正则表达式并不仅限于某一种语言,但是在每种语言中有细微的差别. Java 正则表达式和 Perl 的是最为相似 ...

  3. Java中有哪些语法糖?

    不要你写汇编,Java句句是糖 不能同意上面的这句话,要说为什么,首先要定义下面要讲的“语法糖”. 语法糖指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,并没有给语言添加什么新东西,但是 ...

  4. AMBA APB总线

    前面分析了AHB总线协议.接下来分析APB总线协议. (一) APB总线接口: PCLK APB总线时钟. PRESETn APB总线复位.低有效. PADDR 地址总线. PSELx 从设备选择. ...

  5. mac下最简单的删除node方法是什么

    sudo rm -rf /usr/local/{bin/{node,npm},lib/node_modules/npm,lib/node,share/man/*/node.*}

  6. 学习Spring-Session+Redis实现session共享

    1.添加依赖 <dependency> <groupId>org.springframework.session</groupId> <artifactId& ...

  7. REP开发技巧

    这个基本包含了所有的特殊情况 wrap 换行 title显示在每页的固定位置, 第一页需要特殊处理 <DATA> <PDF_TITLE Font="5" Size ...

  8. 使用yocs_velocity_smoother对机器人速度进行限制

    yocs_velocity_smoother是一个速度.加速度限制器,用来防止robot navigation的速度/转速过快,加速度/快减速过大.Bound incoming velocity me ...

  9. redis 频率限制

    方式1: $redis = new Redis(); //以自然时间控制 一自然分钟内超过100次进行限制, 屏蔽多久的时间必须为计数key时间的倍数 $key = 'xxxx'.date('Y-m- ...

  10. [转]MyEclipse内存不足问题

    1.修改eclipse.ini 在Myeclipse安装目录下G:\MyEclipse8.5\Genuitec\MyEclipse 8.5有一个myeclipse.ini配置文件,设置如下: -vma ...