StrangerThings
StrangerThings

Reputation: 43

Call printf with dynamic arguments at runtime

I try to write a wrapper for printf in C++. Of course i found va_list but dont know how its applicable to me because the wrapper will not be called directly. I will show later on.

I parse a script that includes a function with an unknown number of arguments like

ASTNode node(Token(PRINT, PRINT));
consume(PRINT);
consume(LPAREN);
node.make_child(variable()); // <-- formatstring (node.child[int])
while(current_token._type() != RPAREN) {
    consume(COMMA);
    node.make_child(variable()); // <-- the values to replace in formatstring (node.child[int++])
    i++; 
}
consume(RPAREN);
return node;

The first will be the formatstring and the others will be the values to replace in the format string so the function where i execute it will look like

if(node._token()._type() == PRINT) {
    Token formatstring = visit(*node.child[0]);
    Token temp;
    int i = 1;
    while(i < node.child.size()) {
        visit(*node.child[i++]); // <-- the values to replace in formatstring        
    }        
}

and doesnt take any "real" parameter. How can i build a dynamic parameter array using va_list or another method?

Thank you

Edit It seems my question is unclear to someone..

printf is called like printf(formatstring, param1, param2, param3...) and i want to do build the parameters passed after the first parameter (formatstring) in a loop like

// Pseudocode
out("printf(");
out($myformatstring);
int i = 1;
while(i<parameter_count) {
    out(parameter[i++]);
    out(",");
}
out(")");

Upvotes: 0

Views: 2210

Answers (4)

ayoub belatrous
ayoub belatrous

Reputation: 1

this assembly function can call any function pointer with dynamic arguments at runtime provided that the called function has x64 calling convention windows variadic functions expect floats to be doubles

functions that take struct or bigger than 8 bytes types are passed by pointer

return structs usually are passed as a pointer to called function in the first argument

;#define ARG_Type_FLOAT 0
;#define ARG_Type_DOUBLE 0
;#define ARG_Type_SCALAR 2

;extern "C" size_t dynamic_invoke(void* func, size_t arg_count, size_t * arguments, size_t * argument_types);

;extern "C" double dynamic_invoke(void* func, size_t arg_count, size_t * arguments, size_t * argument_types);

;extern "C" float dynamic_invoke(void* func, size_t arg_count, size_t * arguments, size_t * argument_types);

.code
dynamic_invoke PROC
    ; rcx pointer, rdx arg_count, r8 args_pointer, r9 args_types_pointer

    push rbp
    mov rbp, rsp

    ; save non volatile registers

    sub rsp, 32
    sub rsp, 48
    mov qword ptr [rbp - 8], rdi
    mov qword ptr [rbp - 16], rsi
    mov qword ptr [rbp - 24], r12
    mov qword ptr [rbp - 32], r13
    mov qword ptr [rbp - 40], r14
    mov qword ptr [rbp - 48], r15
    
    ; compute stack needed for arguments (overestimated)
    mov r15, rdx
    imul r15, 8
    sub rsp, r15
    ; store allocated stack size in r15
    mov r15, rbp
    sub r15, rsp

    mov r14,    rcx     ; pointer
    mov r13,    rdx     ; arg_count
    mov r12,    r8      ; arg_pointer
    mov r11,    r9      ; args_types_pointer

    mov rdi, 32         ; stack_top_offset
    mov rsi, 0

_loop:
    mov   r10, qword ptr [r11 + rsi * 8]

    ; arg 0
    cmp rsi, 0
    jne skip_0
    mov   rcx, qword ptr [r12 + rsi * 8]
    cmp   r10, 0
    jne   skip_f_0
    movss xmm0, dword ptr [r12 + rsi * 8]
skip_f_0:
    cmp   r10, 1
    jne   skip_d_0
    movsd xmm0, qword ptr [r12 + rsi * 8]
skip_d_0:
skip_0:

    ; arg 1
    cmp rsi, 1
    jne skip_1
    mov rdx, qword ptr [r12 + rsi * 8]
    cmp   r10, 0
    jne   skip_f_1
    movss xmm1, dword ptr [r12 + rsi * 8]
skip_f_1:
    cmp   r10, 1
    jne   skip_d_1
    movsd xmm1, qword ptr [r12 + rsi * 8]
skip_d_1:
skip_1:

    ; arg 2
    cmp rsi, 2
    jne skip_2
    mov r8, qword ptr [r12 + rsi * 8]
    cmp   r10, 0
    jne   skip_f_2
    movss xmm2, dword ptr [r12 + rsi * 8]
skip_f_2:
    cmp   r10, 1
    jne   skip_d_2
    movsd xmm2, qword ptr [r12 + rsi * 8]
skip_d_2:
skip_2:

    ; arg 3
    cmp rsi, 3
    jne skip_3
    mov r9, qword ptr [r12 + rsi * 8]
    ; float
    cmp   r10, 0
    jne   skip_f_3
    movss xmm3, dword ptr [r12 + rsi * 8]
skip_f_3:
    ; double
    cmp   r10, 1
    jne   skip_d_3
    movsd xmm3, qword ptr [r12 + rsi * 8]
skip_d_3:
skip_3:

; stack args
    cmp rsi, 3
    jle skip_rest
    mov r10, qword ptr [r12 + rsi * 8]
    mov qword ptr [rsp + rdi], r10
    add rdi, 8

skip_rest:
    inc rsi
    cmp rsi, r13
    jl _loop

    call r14

    add rsp, r15
    ; restore scratch
    mov rdi, qword ptr [rbp - 8]
    mov rsi, qword ptr [rbp - 16]
    mov r12, qword ptr [rbp - 24]
    mov r13, qword ptr [rbp - 32]
    mov r14, qword ptr [rbp - 40]
    mov r15, qword ptr [rbp - 48]
    pop rbp
    ret
dynamic_invoke ENDP

END

Upvotes: 0

risbo
risbo

Reputation: 188

/* 
  there is example that explains how to use stdarg for a function 
  with variable number of parameters
*/ 



#include <stdio.h>
#include <stdarg.h>

void myfunc(char* fmt, ...);

/* this example folows logic of printf to specify format
    %d - integer
    %c - single character
    %s - string
    other character will be copied to output
*/

int main( int argc, char* argv[])
{
    myfunc("%s %d ABCD %c", "a string", 4, 'D'); 
}


void myfunc(char *fmt, ...)
{
va_list ap;
int d;
char c, *s;

  va_start(ap, fmt);
  while (*fmt)
  {
    if(*fmt == '%')
    { 
       fmt++;
       switch (*fmt)     // detect format characters
       {
         case 's':              /* string format character is recognized*/
             s = va_arg(ap, char *);
             printf("string: %s\n", s);  /* this example just print out paramater, you can do what you want */
         break;

         case 'd':              /* integer format character */
             d = va_arg(ap, int);
             printf("int: %d\n", d);
         break;

         case 'c':              /* char format character*/
            c = (char) va_arg(ap, int);
            printf("char: %c\n", c);
         break;

     default:
       printf("Just copy non format characters %c\n",*fmt);  // copy non format characters after %
       } 
     }
     else
       printf("just copy '%c'\n", *fmt); 

    fmt++;
  }
  va_end(ap);
}

Upvotes: 0

Jarod42
Jarod42

Reputation: 217980

As I understand you have a given format string, and "read/parse" arguments.

You have so 2 problems to handle, handling the format and using correct type for argument.

printf family doesn't support partial replacement (contrary to Qt for example, which allow QString("%1 %2").arg("Hello") resulting into QString("Hello %2") which allow chaining).

So you have to parse the full format string manually:

  • print regular characters.
  • when % is encountered, retrieve the flag format (unless it is %%, in that case display % directly).

  • from flag format, switch to appropriate conversion, something like

     // flagFormat would "simply" be %i, %010d, %4.2f or %+.0e
     switch (format_type) {
         case EFormatType::String: // %s
              printf(flagFormat, to_string(args[i]).c_str()); break;
         case EFormatType::Int: // %i, %d
              printf(flagFormat, to_int(args[i])); break;
         case EFormatType::String: // %f, %F, %e, %g, %
              printf(flagFormat, to_double(args[i])); break;
         // ...
     }
     ++i;
    

Upvotes: 1

risbo
risbo

Reputation: 188

// Pseudocode
// I would like to propose a way to solve the problem, added some stuff here

out("printf("); 

// you should create format string in accordance with types of parameters
$myformatstring = ""   // let suppose  myformatstring is of type "string"
                   // and it has defined operator "+=" (concatenation of strings)
for(int j = 0;j < parametar_count; j++)
{
    switch(parametar[j].type )  // suppose that you have type information of param's 
    {
        case 'i': myformatstring += " %d "; break;
        case 'f': myformatstring += " %f "; break;
        case 's': myformatstring += " %s"; break;
    ...  // you should handle all types you use ...

    }
}
$myformatstring += "\\n\","; // terminate format string and write a comma before 
                             // other arguments...
out($myformatstring);

int i = 1;
while(i<parameter_count) {
   out(parameter[i++]);
   if( i < parameter_count -1 )   // you don't need comma after last parameter 
      out(",");
}
out(");");

Upvotes: 0

Related Questions