最近在为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. AngularJS中实现Model缓存

    在AngularJS中如何实现一个Model的缓存呢? 可以通过在Provider中返回一个构造函数,并在构造函数中设计一个缓存字段,在本篇末尾将引出这种做法. 一般来说,Model要赋值给Scope ...

  2. Kubernetes部署ELK并使用Filebeat收集容器日志

    本文的试验环境为CentOS 7.3,Kubernetes集群为1.11.2,安装步骤参见kubeadm安装kubernetes V1.11.1 集群 1. 环境准备 Elasticsearch运行时 ...

  3. log4Net 高性能写入和CSV格式

    最近在使用log4net,在使用之前我们必须知道文件流是如何操作的,否则就是盲人摸向...,在FileAppender.cs文件里面有LockingModelBase来控制流的锁,默认有3个子类 Ex ...

  4. Js实现页面关键字高亮显示

    <!DOCTYPE HTML> <html lang="en"> <meta http-equiv="Content-Type" ...

  5. Vert.x入门教程之Hello World

    http://blog.csdn.net/caihuangshi/article/details/51648182

  6. 了解 JavaScript (2)- 需要了解的一些概念

    Ajax 是什么 Ajax 是一种创建交互式 Web 应用程序的方式. Ajax 是 Asynchronous JavaScript and XML 缩写(异步的 JavaScript 和 XML), ...

  7. AI通过了艺术创作图灵测试,你根本分不出来作者是不是人

    各位geek朋友们,今年不用再看画了:近年来最大的艺术成就已经发生了. 这项艺术成就的诞生地,不是北京.新加坡.柏林郊区颜料四溅的画室中,不是威尼斯双年展上.请记住它出现的地点:美国新泽西州新布朗斯维 ...

  8. c/c++ 读入一行不确定个数的整数

    假设有一个文件,文件的每一行包括n个整数,整数之间以一个空格隔开,文件总共有m行,但是事先不知道n,m.如何每次从文件中读取一行整数放到一个数组中. 可以分为两步:1.首先从文件中读入一行字符串,2. ...

  9. Hadoop2.2.0分布式安装配置详解[3/3]

    测试启动 按照下面的每一步执行,执行完一定要看输出的信息,注意warn或error或fatal的情况.因为这都是可能是问题出现的地方.出现一个问题,不解决,可能就会影响接下来的测试.这才是真正的工作量 ...

  10. DxO FilmPack for Mac(胶片模拟效果软件)破解版安装

    1.软件简介    DxO FilmPack 是 macOS 系统上由知名的 DxO Labs 出品的一套胶片模拟效果滤镜,拥有数十种电影风格的滤镜,今天和大家分享最新的版本,支持最新的 PhotoS ...