使用 void(*)(void) 指针调用 void function(int) 并准备使用参数堆栈
Calling a void function(int) with a void(*)(void) pointer and prepare stack yourself with arguments
我有一个 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;
}
这个returns:
0x7ffd72ec204c : -1
0x7ffd72ec204c : 0
我希望这个 return -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
的文章 - 这对我有帮助吗?如果是,我该如何使用?
我知道这不是特别好的做法。这只是一个理论练习。
你试图做的是未定义的行为。在标准 C 中,不可能在运行时构造具有任意参数类型的函数调用。您也不能假设变量最终出现在堆栈中的布局(如果有的话)。您甚至不能假设函数参数是在堆栈上传递的。
如果您想做这样的事情,请考虑研究像 libffi 这样的库,它通过为每个平台和每个操作系统实施不同的解决方案来做类似的事情。
您可以插入平台特定的汇编语言代码来完成您想要的。 请注意,它不可移植。
让我们看一下您的程序的两个稍微简化的版本:
版本 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 代码):
#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=]"
.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 , %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 , %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 , %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 [=12=], %eax
addq , %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[=13=]"
.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 , %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 , %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 , %rsp
.seh_stackalloc 48
.seh_endprologue
call __main
leaq func(%rip), %rax
movq %rax, -8(%rbp)
movq -8(%rbp), %rax
call *%rax
movl [=13=], %eax
addq , %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
这与您在该程序的第一个版本中看到的输出相同。
我有一个 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;
}
这个returns:
0x7ffd72ec204c : -1
0x7ffd72ec204c : 0
我希望这个 return -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
的文章 - 这对我有帮助吗?如果是,我该如何使用?
我知道这不是特别好的做法。这只是一个理论练习。
你试图做的是未定义的行为。在标准 C 中,不可能在运行时构造具有任意参数类型的函数调用。您也不能假设变量最终出现在堆栈中的布局(如果有的话)。您甚至不能假设函数参数是在堆栈上传递的。
如果您想做这样的事情,请考虑研究像 libffi 这样的库,它通过为每个平台和每个操作系统实施不同的解决方案来做类似的事情。
您可以插入平台特定的汇编语言代码来完成您想要的。 请注意,它不可移植。
让我们看一下您的程序的两个稍微简化的版本:
版本 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 代码):
#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=]"
.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 , %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 , %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 , %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 [=12=], %eax
addq , %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[=13=]"
.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 , %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 , %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 , %rsp
.seh_stackalloc 48
.seh_endprologue
call __main
leaq func(%rip), %rax
movq %rax, -8(%rbp)
movq -8(%rbp), %rax
call *%rax
movl [=13=], %eax
addq , %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
这与您在该程序的第一个版本中看到的输出相同。