1

I have a void(*)(void) function pointer to a function which is actually a void f(int). The point is, I don't know what kind of function it is at this point, so I can't simply cast it to void(*)(int). I thought I could simply prepare the stack with the argument of the function.

#include <stdio.h>

void func(int x) {
    printf("%p : %d\n", &x, x);
}

int main(int argc, char* argv[]) {
    int* ptr = &argc - 8;
    *ptr = -1;
    printf("%p : %d\n", ptr, *ptr);

    void (*f)(void) = (void(*)(void)) &func;
    f();

    return 0;
}

This returns:

0x7ffd72ec204c : -1
0x7ffd72ec204c : 0

I would expect this to return -1 twice. However, it seems that gcc adds some code to clear the stack when a function is called, which is why func() shows 0 instead.

Is there some kind of compiler flag to use to disable that?

I'm using gcc (Debian 4.9.2-10) 4.9.2, but if I can get it to work in any compiler that would be enough! My uname is Linux 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt20-1+deb8u3 (2016-01-17) x86_64 GNU/Linux.

I've read about MUST_PASS_IN_STACK - is that something that could help? How do I use it if yes?

I know this is not particularly best practice. This is just a theoretical exercise.

  • 2
    You assume that the arguments are passed on the stack to begin with. They don't have to be :) – Kuba hasn't forgotten Monica Jan 29 '16 at 15:47
  • @KubaOber hm, interesting, but that's why I also print the address - since the addresses are the same, shouldn't the arguments be passed on the stack? If not, where else may they be passed? –  Jan 29 '16 at 15:48
  • @CamilStaps: In registers. You might want to try creating the `f` variable *before* modifying `&argc - 8`, as that will push a new variable onto the stack and maybe mess up what you've written. Note though, that this is all undefined behavior and the compiler is free to destroy your program. – Cornstalks Jan 29 '16 at 15:48
  • @Cornstalks al right, that makes sense - is there a way to disable that with compiler flags or in the `main` function? –  Jan 29 '16 at 15:49
  • Which system, which compiler? This is undefined behaviour, but you might get it to work, on some systems. – Thomas Padron-McCarthy Jan 29 '16 at 15:49
  • The arguments can be passed on the stack, in the registers or in static memory locations. It's all platform-dependent. – Kuba hasn't forgotten Monica Jan 29 '16 at 15:50
  • @ThomasPadron-McCarthy thank you, I added the info to the post. I'm using gcc 4.9.2, Linux 3.16.0-4-amd64 (other compilers are fine too, but I'd like it to work on this system). –  Jan 29 '16 at 15:51
  • The `MUST_PASS_IN_STACK` tells you what types must be passed on the stack. It does not allow you to modify the calling convention. You should not attempt to do that. Read the source code of the libffi for how to do what you want. – fuz Jan 29 '16 at 16:05
  • Implementing lambda capture in C? – user3528438 Jan 29 '16 at 18:16
  • @user3528438 if you must know, but I'm not proud of this :) : I'm trying to make an interface between [Clean](http://clean.cs.ru.nl/Clean) and C in a way that you can call arbitrary C functions from Clean by passing the name of the function as a string. That works, but now I need to be able to add parameters to it. It's a horrible project, shouldn't ever be used in practice, but still fun. –  Jan 29 '16 at 18:22
  • @CamilStaps Then all you need to do is to remove the second `void` from `void (*f)(void)`. https://ideone.com/nDnJiu Sadly, this is a typical x-y problem. More info: http://stackoverflow.com/questions/51032/is-there-a-difference-between-foovoid-and-foo-in-c-or-c – user3528438 Jan 29 '16 at 18:55
  • @user3528438 thank you, but that is not enough for what I need. One of the issues is that the number of arguments to the function may in the end be variable. I'm now working with libffi as FUZxxl suggested and that is working pretty well so far. Thanks though, I'm sorry I didn't highlight all relevant issues in the question - I didn't want it to be too long, and wasn't sure how much would be needed to get an accurate answer. –  Jan 29 '16 at 19:09

2 Answers2

1

What you are trying to do is undefined behaviour. In standard C it is not possible to construct a function call with arbitrary argument types at runtime. You cannot make assumptions about the layout in which variables end up on the stack (if at all) either. You cannot even assume that function arguments are passed on the stack.

If you want to do something like this, consider looking into libraries like the libffi which does something like that by implementing a different solution for each platform and each operating system.

fuz
  • 88,405
  • 25
  • 200
  • 352
1

You can insert platform specific assembly language code to accomplish what you want. Just be warned that it is not portable.

Let's see two versions of a slightly simplified version of your program:

Version 1 (clean code):

#include <stdio.h>

void func(int x) {
    printf("%p : %d\n", &x, x);
}

int main() {
   void (*f)(int) = &func;
   f(-2);
   return 0;
}

Version 2 (hackish code):

#include <stdio.h>

void func(int x) {
    printf("%p : %d\n", &x, x);
}

int main() {
   void (*f)(void) = (void (*)(void))&func;
   return 0;
}

You can use gcc -S to generate the assembly code for both versions.

Assembly code for version 1 in my environment:

    .file   "soc.c"
    .section .rdata,"dr"
.LC0:
    .ascii "%p : %d\12\0"
    .text
    .globl  func
    .def    func;   .scl    2;  .type   32; .endef
    .seh_proc   func
func:
    pushq   %rbp
    .seh_pushreg    %rbp
    movq    %rsp, %rbp
    .seh_setframe   %rbp, 0
    subq    $32, %rsp
    .seh_stackalloc 32
    .seh_endprologue
    movl    %ecx, 16(%rbp)
    movl    16(%rbp), %eax
    movl    %eax, %r8d
    leaq    16(%rbp), %rdx
    leaq    .LC0(%rip), %rcx
    call    printf
    nop
    addq    $32, %rsp
    popq    %rbp
    ret
    .seh_endproc
    .def    __main; .scl    2;  .type   32; .endef
    .globl  main
    .def    main;   .scl    2;  .type   32; .endef
    .seh_proc   main
main:
    pushq   %rbp
    .seh_pushreg    %rbp
    movq    %rsp, %rbp
    .seh_setframe   %rbp, 0
    subq    $48, %rsp
    .seh_stackalloc 48
    .seh_endprologue
    call    __main
    leaq    func(%rip), %rax
    movq    %rax, -8(%rbp)
    movq    -8(%rbp), %rax
    movl    $-2, %ecx
    call    *%rax
    movl    $0, %eax
    addq    $48, %rsp
    popq    %rbp
    ret
    .seh_endproc
    .ident  "GCC: (GNU) 4.9.3"
    .def    printf; .scl    2;  .type   32; .endef

Assembly code for version 2 in my environment:

    .file   "soc.c"
    .section .rdata,"dr"
.LC0:
    .ascii "%p : %d\12\0"
    .text
    .globl  func
    .def    func;   .scl    2;  .type   32; .endef
    .seh_proc   func
func:
    pushq   %rbp
    .seh_pushreg    %rbp
    movq    %rsp, %rbp
    .seh_setframe   %rbp, 0
    subq    $32, %rsp
    .seh_stackalloc 32
    .seh_endprologue
    movl    %ecx, 16(%rbp)
    movl    16(%rbp), %eax
    movl    %eax, %r8d
    leaq    16(%rbp), %rdx
    leaq    .LC0(%rip), %rcx
    call    printf
    nop
    addq    $32, %rsp
    popq    %rbp
    ret
    .seh_endproc
    .def    __main; .scl    2;  .type   32; .endef
    .globl  main
    .def    main;   .scl    2;  .type   32; .endef
    .seh_proc   main
main:
    pushq   %rbp
    .seh_pushreg    %rbp
    movq    %rsp, %rbp
    .seh_setframe   %rbp, 0
    subq    $48, %rsp
    .seh_stackalloc 48
    .seh_endprologue
    call    __main
    leaq    func(%rip), %rax
    movq    %rax, -8(%rbp)
    movq    -8(%rbp), %rax
    call    *%rax
    movl    $0, %eax
    addq    $48, %rsp
    popq    %rbp
    ret
    .seh_endproc
    .ident  "GCC: (GNU) 4.9.3"
    .def    printf; .scl    2;  .type   32; .endef

The only difference between the assembly code of the two versions is line 44.

   movl    $-2, %ecx

If you inject the same assembly code into the second version of the program as:

#include <stdio.h>

void func(int x) {
    printf("%p : %d\n", &x, x);
}

int main() {
   void (*f)(void) = (void (*)(void))&func;
   __asm__("movl    $-2, %ecx");
   f();
   return 0;
}

the compiler generates the expected assembly code. When I run the above program, I get:

0x22cae0 : -2

which is the same output you would see with the first version of the program.

R Sahu
  • 204,454
  • 14
  • 159
  • 270