使用void(*)(void)指针调用void函数(int)并使用参数

时间:2016-01-29 15:46:04

标签: c gcc stack function-pointers compiler-flags

我有一个void(*)(void)函数指针,指向一个实际为void f(int)的函数。关键是,我现在还不知道它是什么类型的功能,所以我不能简单地将其转换为void(*)(int)。我以为我可以简单地用函数的参数准备堆栈。

#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;
}

返回:

0x7ffd72ec204c : -1
0x7ffd72ec204c : 0

我希望这会两次返回-1。但是,似乎gcc在调用函数时添加了一些代码来清除堆栈,这就是func()显示0的原因。

是否有某种编译器标志用于禁用它?

我使用gcc (Debian 4.9.2-10) 4.9.2,但是如果我能在任何编译器中使用它就足够了!我的名字是Linux 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt20-1+deb8u3 (2016-01-17) x86_64 GNU/Linux

我已经读过关于MUST_PASS_IN_STACK的内容 - 这有什么可以帮助吗?如果是,我该如何使用?

我知道这不是特别好的做法。这只是一个理论上的练习。

2 个答案:

答案 0 :(得分:1)

您要做的是未定义的行为。在标准C中,不可能在运行时使用任意参数类型构造函数调用。你不能对变量最终在堆栈上的布局做出假设(如果有的话)。你甚至不能假设函数参数在堆栈上传递。

如果你想做这样的事情,可以考虑通过为每个平台和每个操作系统实施不同的解决方案来查看类似libffi之类的库。

答案 1 :(得分:1)

您可以插入特定于平台的汇编语言代码来完成您想要的任务。 请注意,它不便携。

让我们看一下程序稍微简化版的两个版本:

版本1(清洁代码):

#include <stdio.h>

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

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

第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;
}

您可以使用gcc -S为两个版本生成汇编代码。

我环境中版本1的汇编代码:

    .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

我环境中版本2的汇编代码:

    .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

两个版本的汇编代码之间的唯一区别是第44行。

   movl    $-2, %ecx

如果将相同的汇编代码注入程序的第二个版本:

#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;
}

编译器生成预期的汇编代码。当我运行上述程序时,我得到:

0x22cae0 : -2

与您在程序的第一个版本中看到的输出相同。