How to prevent LUA scripts that block your program

How to prevent LUA scripts that block your program

Introduction

In the previous two articles about LUA we learned how to embed LUA in C/C++.  But a nasty script can also block the C/C++ application by creating an endless loop.  An example of such a terrible script would be this :

while(true)
do
end


If you execute such a script then your application will hang forever.  Luckily LUA provides hooks and Windows provides threads.  And for people who can't wait : the download is at the bottom of the page :-).

 

Threads

I'm not talking about LUA threads here, but about the threads of your operating system (Win32 in our case).  In order to make the script execution independent of your interface you have to create two threads.  One for the interface and one for the script execution.  If now a bad script will block the program it will only block the executioner thread and not the interface thread.

VERY IMPORTANT NOTE: the code below is only to show how to use a thread to avoid blocking of your program, it doesn't tell how to make LUA thread-safe/multithreaded!  Click on this link to read on how to make LUA multithreaded and avoid crashes.

To enable threads in your program you have to include process.h and you have to tell the compiler you're going to create a multithreaded program, go to the Project Settings, go to the C/C++ tab and select "Code Generation" as the category.

For the debug version select "Debug multithreaded" :

For the release version select "Multithreaded" :

In your program you can start a thread by calling the C library function _beginthread.  For example :

void ThreadRoutine(void *l)
{
  //
  lua_State *L = (lua_State *) l;

  // Do something with LUA
  ;
}

_beginthread(ThreadRoutine, 0, (void *) L);


 

LUA Hooks/Events

Hooks/events are mainly used for debugging your LUA state but we can also use it to abort a running script.  We are interested in a hook/event called "The line hook".  This event will occur every time an instruction is about to be executed.  To set a hook we must call the lua_sethook function, it's defined as follows :

LUA_API int lua_sethook (lua_State *L, lua_Hook func, int mask, int count);

There are as you can see 4 parameters, the first one is (like always) the pointer to the LUA state, the second one is the address of a C function that handles the event.  The third parameter tells us for which kind of events the hook function will be called, the fourth parameter is only used for the LUA_HOOKCOUNT hook, see the manual.

We first create a global variable to tell us wether or not to abort the script.

// At startup: assume we are not aborting
bool bAbortScript = false;


Then we create the following short but very effective hook/event handler :

void HookRoutine(lua_State *L, lua_Debug *ar)
{
  // Only listen to "Hook Lines" events
  if(ar->event == LUA_HOOKLINE)
  {
    // Check the global flag to know if we should abort
    if(bAbortScript)
    {
      // Ok, let's abort the script
      lua_pushstring(L, "HookRoutine: Abort requested!");
      lua_error(L);
    }
  }
}


We will register the hook/event handler with lua_sethook as follows :

// Set a hook for LUA_MASKLINE
lua_sethook(L, HookRoutine, LUA_MASKLINE, 0);


For more information about debugging and other types of hooks please refer to your LUA manual.

 

Modify your functions to protect against blocking scripts

Let's assume we create a script function sleep, the function will call the Windows Sleep function (this function suspends execution for X milliseconds).  We define this glue function as follows :

int sleep_glue(lua_State *L)
{
  // We need 1 parameter, no more, no less
  if(lua_gettop(L) != 1)
  {
    lua_pushstring(L, "sleep: parameter mismatch");
    lua_error(L);
  }

  // Execute the sleep
  DWORD Duration = (DWORD) lua_tonumber(L, 1)
  Sleep(Duration);

  // No results
  return 0;
}


By implementing the function as such you have just given a bad LUA scripter the power to make your application hang for a long time.  The problem is this : if the scripter calls thesleep function with a high value, let's say : sleep(1500000) then your program will hang for 1500 seconds or 25 minutes!

A better implementation would be to sleep only for short moments and to check wether or not the script has been aborted, like this :

int sleep_glue(lua_State *L)
{
  // We need 1 parameter, no more, no less
  if(lua_gettop(L) != 1)
  {
    lua_pushstring(L, "sleep: parameter mismatch");
    lua_error(L);
  }

  // Execute the sleep (per 100ms)
  DWORD Duration = (DWORD) lua_tonumber(L, 1);
  while(Duration > 100)
  {
    // Abort if necessary...
    if(bAbortScript)
      return 0;

    // Sleep 100ms
    Sleep(100);
    Duration = Duration - 100;
  }

  // Execute the remaing part
  // Don't execute it if the script is being aborted...
  if(!bAbortScript)
    Sleep(Duration);

  // No results
  return 0;
}


In the sleep function as above, the sleep is executed for a short duration of 100ms.  So if we want to suspend execution for 5 seconds (5000 milliseconds), then the Sleep function will be called 50 times, so every 50 times we check wether to abort the script/sleep function or not.

 

Event Objects

The sleep function defined above isn't that optimal, the program is executing a continuous loop eating CPU time.  Windows provides a better technique to avoid this: waitable event objects.  An event object allows threads to notify each other that some kind of an event has occured.  Windows has a function WaitForSingleObject it acts like Sleep but it will abort the function if the state of an event object is signaled (signaled means that the event has occured).

We create a global handle varible hCloseProgram to represent the event :

volatile HANDLE hCloseProgram; // an event handle


In the start-up of your program you create the event object using the CreateEvent function :

HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttribute, BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName);

There are four parameters.  The first one represents the security attributes, we simple use NULL, the second one tells us we will control the event manually, the 3th parameter will be FALSE and that means that the initial state of the events is non-signaled, and the last parameter is a user-defined name (which can be NULL to create a nameless event) :

hCloseProgram = CreateEvent(NULL, TRUE, FALSE, NULL);


Our new glue function becomes this :

int sleep_glue(lua_State *L)
{
  // We need 1 parameter, no more, no less
  if(lua_gettop(L) != 1)
  {
    lua_pushstring(L, "sleep: parameter mismatch");
    lua_error(L);
  }

  // Wait for X milliseconds
  // Or return immediatly if hCloseProgram is signaled
  DWORD Duration = (DWORD) lua_tonumber(L, 1);
  WaitForSingleObject(hCloseProgram, Duration);

  // No results
  return 0;
}


And in order to cancel the WaitForSingleObject, all you do is :

SetEvent(hCloseProgram);


Of course, event objects still don't abort the LUA script executioner.  You also need to modify your hook function to teach it how to use the event object :

void HookRoutine(lua_State *L, lua_Debug *ar)
{
  if(ar->event == LUA_HOOKLINE)
  {
    if(WaitForSingleObject(hCloseProgram, 0) == WAIT_OBJECT_0)
    {
      // Ok, let's quit the program
      lua_pushstring(L, "HookRoutine: Abort requested!");
      lua_error(L);
    }
  }
}


But the be honest, that's not a method I would recommend, the hook routine will be called a lot and calling every time the WaitForSingleObject function causes it to slow down and thus slowing down your scripts!  A better method would be a combination of both a global variable to allow aborting of the script engine and an event to allow aborting of functions that suspend the system.  See the LUA Demo 3 program to see how it is done.

I also invite you to experiment with another method : you can for example set only the LUA hook routine when you need to close your program.  For example :

void AbortRoutine(lua_State *L, lua_Debug *ar)
{
  if(ar->event == LUA_HOOKLINE)
  {
    // Ok, let's abort the script
    lua_pushstring(L, "AbortRoutine has been called!");
   lua_error(L);
   }
}

void OnQuit()
{
  // Set a hook for LUA_MASKLINE
  lua_sethook(L, AbortRoutine, LUA_MASKLINE, 0);
  
  // Tell the wait functions that we're quiting
  SetEvent(hCloseProgram);
}


 

About LUA Demo 3

This demo project is a bit more complicated than the other LUA downloadable examples.  It's an application that allows you to enter an LUA script that will be executed, either using a seperate thread or using only the main thread.  If no seperate thread is used the program will exit at the end of the script execution.  And in case of a seperate thread, the main thread will be suspended indefinitly (INFINITE) (until hCloseProgram becomes signaled).  This little test project is also safe against blocking scripts.  It includes most of the programming techniques explained above, feel free to browse and use the source code.  The program will also warn the user if the LUA script took too long to shut down (which normally shouldn't happen).

It's a nice LUA demonstration project, the following LUA functions are created :

clrscr( ) = clear the screen
getx( ) = get the current x coordinate
gety( ) = get the current y coordinate
gotoxy( ) = change the cursor position
printf( ) = print text
kbhit( ) = has a key been hit?
delay( ) = the sleep function (but in traditional conio.h style)
exit ( ) = exit the script

The program also allows you to save & load scripts, have you made a nice script with it?  Please e-mail it to me and show me what you've created with my litle demo program.  Thanks.

 

Download

You can download the above test project here (compressed ZIP files) :

The source code of LUA Demo 3
The executable version of LUA Demo 3

NOTE: All the required files are included in the source code file in order to compile succesfully

IMPORTANT: Even though the download contains multithreaded code, the LUA state isn't thread-safe in this example. Full LUA multithreading & thread-safety is discussed in this article (follow the link)!

 

Contact

If you have questions or remarks about this article or its contents, then feel free to contact me at <fibergeek @ codegurus.be>.  Don't forget to remove the white spaces or the e-mail won't arrive.

 http://www.codegurus.be/codegurus/programming/luahooks_en.htm

To create a PAL decoder block in GNU Radio Companion (GRC), you can follow these steps: 1. Open GNU Radio Companion and create a new flow graph. 2. Drag a "QT GUI Frequency Sink" block onto the flow graph canvas and connect it to the output of your PAL decoder block. This will allow you to visualize the output of your decoder. 3. Drag a "Feedforward AGC" block onto the flow graph canvas and connect it to the output of your PAL decoder block. This block will adjust the gain of the signal to a constant level. 4. Drag a "Low Pass Filter" block onto the flow graph canvas and connect it to the output of the AGC block. This block will remove high-frequency noise from the signal. 5. Drag a "Throttle" block onto the flow graph canvas and connect it to the output of the low pass filter block. This block limits the rate at which the signal is processed to prevent overloading the CPU. 6. Drag a "QT GUI Time Sink" block onto the flow graph canvas and connect it to the output of the throttle block. This block will allow you to visualize the output of the decoder in the time domain. 7. Finally, add a "PAL Decoder" block to the flow graph. You can find this block in the "Digital" section of the block library. Connect the input of the decoder block to your PAL signal source and the output to the input of the AGC block. 8. Configure the PAL Decoder block according to your signal's characteristics, such as the frequency offset and the type of PAL signal being used. 9. Save the flow graph and run it to see the output of your PAL decoder block. Note that the specific steps may vary depending on your signal source and the requirements of your PAL decoder.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值