【问题标题】:Calling a void function(int) with a void(*)(void) pointer and prepare stack yourself with arguments使用 void(*)(void) 指针调用 void function(int) 并使用参数自己准备堆栈
【发布时间】:2016-05-07 10:05:23
【问题描述】:

我有一个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 的文章——这有帮助吗?如果是,我该如何使用它?

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

【问题讨论】:

  • 您假设参数是在堆栈上传递的。他们不必是:)
  • @KubaOber 嗯,有趣,但这就是为什么我也打印地址 - 因为地址是相同的,参数不应该在堆栈上传递吗?如果没有,它们还能在哪里通过?
  • @CamilStaps:在寄存器中。您可能想在修改&amp;argc - 8 之前尝试创建f 变量,因为这会将一个新变量推入堆栈,并且可能会弄乱您编写的内容。但请注意,这都是未定义的行为,编译器可以随意破坏您的程序。
  • @Cornstalks 好吧,这是有道理的 - 有没有办法通过编译器标志或在 main 函数中禁用它?
  • 哪个系统,哪个编译器?这是未定义的行为,但您可能会在某些系统上使其正常工作。

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


【解决方案1】:

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

如果您想做这样的事情,请考虑研究像 libffi 这样的库,它通过为每个平台和每个操作系统实施不同的解决方案来完成类似的事情。

【讨论】:

    【解决方案2】:

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

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

    版本 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(骇客代码):

    #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
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-03-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-30
      • 1970-01-01
      • 1970-01-01
      • 2020-06-11
      相关资源
      最近更新 更多