News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

Array of functions .OR. Array of function addresses ?

Started by frktons, June 30, 2010, 01:00:08 AM

Previous topic - Next topic

frktons

Hi guys.

For a task I've in my mind, I'm trying to build some code
around an old problem:

- A progressive number from 0 to 255 [1 byte]

- A list of functions/procedures that I would like to identify with a progressive number
but at the same time having a proper name.

- An array of addresses containing the function/procedure addresses

- The possibility to call a function/procedure knowing 4 things:

    1. the progressive number of the function
    2. the parameter1
    3. the parameter2
    4. an optional return parameter3

Maybe this is a more advanced topic than I can actually digest.
But I would be glad if anyone can elaborate a little upon this matter.

As far as I know it is possible to do that in C [that I'm actually studying],
so it should be possible/better/more efficient to do it in MASM as well.

C example:



  #include<stdio.h>

  // -- FUNCTION PROTOTYPES --
  void func1();
  void func2();
  void func3();
  void func4();
  void func5();


  void main()
  {

   void (*ptr[5])();


   ptr[0]=func1;
   ptr[1]=func2;
   ptr[2]=func3;
   ptr[3]=func4;
   ptr[4]=func5;


   for(int i=0;i<5;i++)
     (*ptr[i])();
  }

  // -- FUNCTIONS DEFINITION --
  void func1()
  {
   printf("Called Func1!\n");
  }

  void func2()
  {
   printf("Called Func2!\n");
  }

  void func3()
  {
   printf("Called Func3!\n");
  }

  void func4()
  {
   printf("Called Func4!\n");
  }

  void func5()
  {
   printf("Called Func5!\n)";
  }




Any suggestion?

Thanks

Mind is like a parachute. You know what to do in order to use it :-)

Rockoon


table dd offset func0, offset func1, offset func2, ...

...

mov eax, function_index
call [table + 4 * eax]
When C++ compilers can be coerced to emit rcl and rcr, I *might* consider using one.

frktons

Quote from: Rockoon on June 30, 2010, 01:06:43 AM

table dd offset func0, offset func1, offset func2, ...

...

mov eax, function_index
call [table + 4 * eax]


Thanks Rock,

this would be great for calling procedures without parameters.  ::)
If some parameters are needed ?
Mind is like a parachute. You know what to do in order to use it :-)

hutch--

Frank,

It would be something like a table with a fixed number of parameters that held the largest count of arguments you would pass to any function address in the range you are calling. If for example you were going to call at the most, a 4 argument function you can also use that same table for 3, 2, 1 and no arguments.

To maintain access speed to the table of functions I would be inclined to have an array of pointers to the table so that you just change an array index to select the address and arguments for each function that you require.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

Rockoon

Quote from: frktons on June 30, 2010, 01:11:03 AM
If some parameters are needed ?

Push them before the call.

Seriously... how is this not obvious?
When C++ compilers can be coerced to emit rcl and rcr, I *might* consider using one.

frktons

Quote from: hutch-- on June 30, 2010, 01:20:23 AM
Frank,

It would be something like a table with a fixed number of parameters that held the largest count of arguments you would pass to any function address in the range you are calling. If for example you were going to call at the most, a 4 argument function you can also use that same table for 3, 2, 1 and no arguments.

To maintain access speed to the table of functions I would be inclined to have an array of pointers to the table so that you just change an array index to select the address and arguments for each function that you require.

Thanks Hutch.

Yes this is a good starting point.  :thumbu
I'm thinking about this structure in order to retain access speed
to the functions/procedures avoiding multiple cmps.

Frank

Quote from: Rockoon on June 30, 2010, 01:25:00 AM
Quote from: frktons on June 30, 2010, 01:11:03 AM
If some parameters are needed ?

Push them before the call.

Seriously... how is this not obvious?


What about being an ASM n00b? I'm at the beginning of my learning
path, so what is obvious for the most of you, it's not for the most of me :-)

Enjoy and thanks for your patience and help.
Mind is like a parachute. You know what to do in order to use it :-)

hutch--

Frank,

Rockons idea is a viable option, store purely the addresses of the functions in an array addressed by an array of pointers so you can simply nominate the function you want by a single array index then push the arguments you need for each function locally where you call it from. It means you have to keep track of the argument count and push them in the right order but that should not be a big deal from the local calling position where you have that data at hand.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

Ghandi

Why not typedef the functions and then use a switch or jump table to identify which invoke to call?

Or even using a jump table to decide which push to land on, locals to store parameters and a single CALL DWORD PTR [dwCallTable + EAX * 4]?

HR,
Ghandi

clive

Quote from: frktons on June 30, 2010, 01:11:03 AM
this would be great for calling procedures without parameters.  ::)
If some parameters are needed ?

Well a) you could PUSH them, or b) you could reference the stack frame of the caller

So using classic procedure calling :


top PROC Index:DWORD, Param1:DWORD, Param2:DWORD, Param3:DWORD
  push Param3
  push Param2
  push Param1
  mov eax, Index
  call [Table + 4 * eax]
  add esp,12
  ret


Or the fact it is assembler and you can control the prologue/epilogue yourself, and refer back to a different/original frame :


top PROC ; Parameters Index, Param1, Param2, Param3
  push ebp
  mov ebp,esp ; Frame of reference
  mov eax,[ebp+8] ; Index
  call [Table + 4 * eax]
  pop ebp
  ret
top ENDP

func0 PROC
  mov edx,[ebp+12] ; Param1, referencing the frame defined by the caller
  mov ecx,[ebp+16] ; Param2
  mov eax,[ebp+20] ; Param3, returning in EAX
  ret
func0 ENDP
It could be a random act of randomness. Those happen a lot as well.

Rockoon

clive, if you are going to have a middle-man function then its common to just jump to the destination, rather than call it:

top:
  pop edx ; return addr
  pop eax ; index
  push edx
  jmp [table + 4 * eax]

When C++ compilers can be coerced to emit rcl and rcr, I *might* consider using one.

frktons

Quote from: clive on June 30, 2010, 04:24:57 AM
Quote from: frktons on June 30, 2010, 01:11:03 AM
this would be great for calling procedures without parameters.  ::)
If some parameters are needed ?

Well a) you could PUSH them, or b) you could reference the stack frame of the caller

So using classic procedure calling :


top PROC Index:DWORD, Param1:DWORD, Param2:DWORD, Param3:DWORD
  push Param3
  push Param2
  push Param1
  mov eax, Index
  call [Table + 4 * eax]
  add esp,12
  ret


Or the fact it is assembler and you can control the prologue/epilogue yourself, and refer back to a different/original frame :


top PROC ; Parameters Index, Param1, Param2, Param3
  push ebp
  mov ebp,esp ; Frame of reference
  mov eax,[ebp+8] ; Index
  call [Table + 4 * eax]
  pop ebp
  ret
top ENDP

func0 PROC
  mov edx,[ebp+12] ; Param1, referencing the frame defined by the caller
  mov ecx,[ebp+16] ; Param2
  mov eax,[ebp+20] ; Param3, returning in EAX
  ret
func0 ENDP


Nice example, thanks.

Quote from: Rockoon on June 30, 2010, 10:53:07 AM
clive, if you are going to have a middle-man function then its common to just jump to the destination, rather than call it:

top:
  pop edx ; return addr
  pop eax ; index
  push edx
  jmp [table + 4 * eax]



Well, it's good to have 2 different ways of performing the same task.
What about the speed and any other consideration about the two models?

Mind is like a parachute. You know what to do in order to use it :-)

clive

Rockoon's is cleverer, and would permit you to use regular C calling convention procedures for you sub-functions, and not re-stack the parameters.

My a) suggestion is the most expensive as it re-stacks the parameter, but is simpler to understand. There are lots of ways to skin the cat, depending on what it is you really want to achieve. Go with whatever works for you, and when you understand things better other solutions may become apparent.

-Clive
It could be a random act of randomness. Those happen a lot as well.

Rockoon


The C calling convention is CDECL,

There are some calling conventions that would be tricky (auxiliary space required,) because you basically cannot guarantee a single across-the-board free register. Stiill, the method could be improved to blindly support quite a few calling conventions:

top:
  pop eax
  xchg eax, [esp]
  jmp [table + 4 * eax]


this would be compatible with CDECL, FASTCALL*, STDCALL, PASCAL, SAFECALL, THISCALL**, and I assume some others.

* Only 2+ parameter functions, otherwise the extra function index will not be on the stack.
** The Microsoft variant of THISCALL

When C++ compilers can be coerced to emit rcl and rcr, I *might* consider using one.

frktons

Quote from: Ghandi on June 30, 2010, 03:12:12 AM
Why not typedef the functions and then use a switch or jump table to identify which invoke to call?

Or even using a jump table to decide which push to land on, locals to store parameters and a single CALL DWORD PTR [dwCallTable + EAX * 4]?

HR,
Ghandi

In order to understand your proposal it's better if you show me
a sample code, and maybe add some more comments about it.
Otherwise When it'll be clear in my mind I'll consider your suggestion. :-)

Quote from: clive on June 30, 2010, 04:44:29 PM
Rockoon's is cleverer, and would permit you to use regular C calling convention procedures for you sub-functions, and not re-stack the parameters.

My a) suggestion is the most expensive as it re-stacks the parameter, but is simpler to understand. There are lots of ways to skin the cat, depending on what it is you really want to achieve. Go with whatever works for you, and when you understand things better other solutions may become apparent.

-Clive

Thanks clive, a good explanation is always welcome.
The procedure's aim will be to get a byte from a string or
whatever source [external file maybe] and use it like an
opcode to jump or call a sub-function that will really do
the job with the passed parameters.

I'm actually studying this problem in C and when I'll finish
I will try to convert it in MASM or INLINE ASM.
The C code I've put togheter at the moment is something
like:

//-------------------------------------------------------------------
// Program to demonstrate
// array of functions
//-------------------------------------------------------------------
  #include<stdio.h>
  #undef getchar
  #pragma comment(lib, "\\masm32\\lib\\msvcrt.lib")

// -- FUNCTION PROTOTYPES --

  void func1(char, int);
  void func2(char, int);
  void func3(char, int);
  void func4(char, int);
  void func5(char, int);
  int count = 0;

  int main(void)
  {

   void (*ptr[5])(char, int);

//-------------------------------------------------------------------
// arrays are made to point
// at the respective functions
//-------------------------------------------------------------------
   ptr[0]=&func1;
   ptr[1]=&func2;
   ptr[2]=&func3;
   ptr[3]=&func4;
   ptr[4]=&func5;

// -------------------------------------------------------------------
// now the array elements
// point to different functions
// which are called just like
// we access the elements of
// an array
//-------------------------------------------------------------------
   for(int i=0;i<5;i++)
     ptr[i]('A',(int) ' ');
   return 0;
  }

  // -- FUNCTIONS DEFINITION --

  void func1(char token, int times)
  {
   printf("Called Func1!\n");
   printf("Token = \'%c\' - to repeat %d times\n",token,times);
   for (count=0;count<times;count++)
      printf("%c",token);
   printf("\n Func1! End \n");
  }

  void func2(char token, int times)
  {
   printf("Called Func2!\n");
   printf("Token = \'%c\' - to repeat %d times incremented by 1\n",token,times);
   for (count=0; count<times + 1; count++)
      printf("%c",token);
   printf("\n Func2! End \n");
  }

  void func3(char token,int times)
  {
   printf("Called Func3!\n");
   printf("Token = \'%c\' - to repeat %d times doubled\n",token,times);
   for (count=0; count<times; count++)
      printf("%c%c",token,token);
   printf("\n Func3! End \n");
  }

  void func4(char token, int times )
  {
   printf("Called Func4!\n");
   printf("Token = \'%c\' - to repeat %d times doubled except the last time\n",token,times);
   for (count=0; count<times -1; count++) {
      printf("%c%c",token,token);
      printf("%c",token);
   }
   printf("\n Func4! End \n"); 
  }

  void func5(char token, int times)
  {
   printf("Called Func5!\n");
   printf("Token = \'%c\' - to repeat %d times tripled\n",token,times);
   for (count=0; count<times; count++)
      printf("%c%c%c",token,token,token);
   printf("\n Func5! End \n");
  }


So I have essentially 3 bytes in a string:

1. first byte is the number of the function/procedure to call or jump to
2. second byte is the character or group of characters to process
3. third byte is the amount of work to do with the second parameter

It's a sort of internal interpreter the program uses to code the steps to
perform a task. What I'm really trying to do is to have a short string
inside the program that defines the steps to build a larger string, like
an internal compression/decompression procedure to spare space at
a limited performance cost. 

Quote from: Rockoon on June 30, 2010, 05:27:09 PM

The C calling convention is CDECL,

There are some calling conventions that would be tricky (auxiliary space required,) because you basically cannot guarantee a single across-the-board free register. Stiill, the method could be improved to blindly support quite a few calling conventions:

top:
  pop eax
  xchg eax, [esp]
  jmp [table + 4 * eax]


this would be compatible with CDECL, FASTCALL*, STDCALL, PASCAL, SAFECALL, THISCALL**, and I assume some others.

* Only 2+ parameter functions, otherwise the extra function index will not be on the stack.
** The Microsoft variant of THISCALL



Thanks Rock for the wealth of info you are providing.  :U

Mind is like a parachute. You know what to do in order to use it :-)