0

I know there are lots of questions here about functions that take a variable number of arguments. I also know there's lots of docs about stdarg.h and its macros. And I also know how printf-like functions take a variable number of arguments. I already tried each of those alternatives and they didn't help me. So, please, keep that in mind before marking this question as duplicate.

I'm working on the process management features of a little embedded operating system and I'm stuck on the design of a function that can create processes that run a function with a variable number of parameters. Here's a simplified version of how I want my API to looks like:

// create a new process
// * function is a pointer to the routine the process will run
// * nargs is the number of arguments the routine takes
void create(void* function, uint8_t nargs, ...);

void f1();
void f2(int i);
void f3(float f, int i, const char* str);

int main()
{
    create(f1, 0);
    create(f2, 1, 9);
    create(f3, 3, 3.14f, 9, "string");

    return 0;
}

And here is a pseudocode for the relevant part of the implementation of system call create:

void create(void* function, uint8_t nargs, ...)
{
    process_stack = create_stack();
    first_arg = &nargs + 1;
    copy_args_list_to_process_stack(process_stack, first_arg);
}

Of course I'll need to know the calling convention in order to be able to copy from create's activation record to the new process stack, but that's not the problem. The problem is how many bytes do I need to copy. Even though I know how many arguments I need to copy, I don't know how much space each of those arguments occupy. So I don't know when to stop copying.

The Xinu Operating System does something very similar to what I want to do, but I tried hard to understand the code and didn't succeed. I'll transcript a very simplified version of the Xinu's create function here. Maybe someone understand and help me.

pid32 create(void* procaddr, uint32 ssize, pri16 priority, char *name, int32 nargs, ...)
{
    int32        i;
    uint32      *a;     /* points to list of args   */
    uint32  *saddr;     /* stack address        */

    saddr = (uint32 *)getstk(ssize); // return a pointer to the new process's stack

    *saddr = STACKMAGIC; // STACKMAGIC is just a marker to detect stack overflow

    // this is the cryptic part
    /* push arguments */
    a = (uint32 *)(&nargs + 1);     /* start of args        */
    a += nargs -1;                  /* last argument        */
    for ( ; nargs > 4 ; nargs--)    /* machine dependent; copy args */
        *--saddr = *a--;            /* onto created process's stack */
    *--saddr = (long)procaddr;
    for(i = 11; i >= 4; i--)
        *--saddr = 0;
    for(i = 4; i > 0; i--) {
        if(i <= nargs)
            *--saddr = *a--;
        else
            *--saddr = 0;
    }
}

I got stuck on this line: a += nargs -1;. This should move the pointer a 4*(nargs - 1) ahead in memory, right? What if an argument's size is not 4 bytes? But that is just the first question. I also didn't understand the next lines of the code.

  • Well, as the source code comment states, a+=nargs-1 makes 'a' to point to the last argument, independently of pointer size. If the argument size is not 4 bytes, a will still point to the last argument. Then, on the next line, the first 4 arguments are skipped, and the rest is pushed on the stack. Then procaddr is pushed. Then zeroes are pushed for arg[12] down to arg[max(4,nargs)]. Then arg[max(4,nargs)-1] down to arg[0] are pushed, i.e. the first 4 args are pushed last, and this particular code seems to assume max 12 arguments. The argument size is sizeof(void*). – Laszlo Mar 15 '18 at 02:14
  • This is the "inverse varargs problem". See [here](http://c-faq.com/varargs/invvarargs.html). Also read up on ["foreign function interfaces"](https://en.wikipedia.org/wiki/Foreign_function_interface). – Steve Summit Mar 15 '18 at 06:30
  • Possible duplicate of [C late binding with unknown arguments](https://stackoverflow.com/questions/34885868/c-late-binding-with-unknown-arguments) – Steve Summit Mar 15 '18 at 06:36
  • @Laszlo, I think I may be missing something here. How 'a' still points to the last argument even when we have arguments with sizes different than 4 bytes? Arguments passed by value occupy sizeof(arg_type) bytes in the stack, am I wrong? –  Mar 15 '18 at 15:34
  • rrd, yes, you're right. I don't see why sizeof(arg_type) needs to be 4 bytes. – Laszlo Mar 15 '18 at 17:16

1 Answers1

0

If you are writing an operating system, you also define the calling convention(s) right? Settle for argument sizes of sizeof(void*) and pad as necessary.

Laszlo
  • 769
  • 9
  • 19
  • Could you please talk more about your answer. I didn't understand how the function create could know where the padding is necessary. –  Mar 16 '18 at 22:39
  • I think the first link provided by Steve Summit above has the explanation, i.e. use an array of pointers to void to hold the argument list. Pointers and integral types you can pass then by value, and everything else (floating point numbers, user defined types, strings and arrays) you can pass by reference. Hope it helps. – Laszlo Mar 19 '18 at 12:41