在我的C++ JNI-Agent项目中,我正在实现一个函数,该函数将接收可变数量的参数,并将执行传递给另一个函数:
// address of theOriginalFunction
public static void* originalfunc;
void* interceptor(JNIEnv *env, jclass clazz, ...){
// add 4 to the function address to skip "push ebp / mov ebp esp"
asm volatile("jmp *%0;"::"r" (originalfunc+4));
// will not get here anyway
return NULL;
}
上述函数只需跳转到:
JNIEXPORT void JNICALL Java_main_Main_theOriginalFunction(JNIEnv *env, jclass clazz, jboolean p1, jbyte p2, jshort p3, jint p4, jlong p5, jfloat p6, jdouble p7, jintArray p8, jbyteArray p9){
// Do something
}
上述代码完美运行,原始函数可以正确读取所有参数(已测试包括数组等不同类型的9个参数)。
然而,在从拦截器跳转到原始函数之前,我需要进行一些计算。但是,这里我观察到了有趣的行为。
void* interceptor(JNIEnv *env, jclass clazz, ...){
int x = 10;
int y = 20;
int summ = x + y;
// NEED TO RESTORE ESP TO EBP SO THAT ORIGINAL FUNCTION READS PARAMETERS CORRECTLY
asm (
"movl %ebp, %esp;"
"mov %rbp, %rsp"
);
// add 4 to the function address to skip "push ebp / mov ebp esp"
asm volatile("jmp *%0;"::"r" (originalfunc+4));
// will not get here anyway
return NULL;
}
这仍然有效,我能够进行一些基本的计算,然后重置堆栈指针并跳转到我的原始函数,原始函数也正确读取var_args中的参数。但是:如果我用malloc
或printf(“任何字符串”)
替换基本的int操作,那么,不知何故,如果跳转到我的原始函数,那么我的参数就会混乱,并且原始函数最终读取错误的值...
我已经尝试调试此行为并检查内存区域以查看出了什么问题...在跳转之前,那里的一切看起来都很好,ebp后面跟着函数参数。
如果没有复杂的计算就进行跳转,则一切都正常工作,ebp后面的内存区域不会更改。原始函数读取正确的值...
现在,如果我在执行printf(例如)后进行跳转,原始方法读取的参数将被破坏...
是什么导致了这种奇怪的行为?printf甚至不在我的方法中存储任何局部变量...好吧,它确实在寄存器中存储一些文字常量,但是为什么我的堆栈只有在跳转之后才会被破坏,而不是在之前就已经破坏了呢?
我在这个项目中使用的是运行在Windows机器上的g++ 4.9.1编译器。
是的,我关心std::forward和模板选项,但它们在我的情况下不起作用...还有,我知道跳转到其他方法有点hacky,但这是我使JNI-interceptor工作的唯一想法...
******************** 编辑 ********************
如讨论所述,我正在添加带有源函数的生成汇编代码。
没有printf的函数(正常工作):
void* interceptor(JNIEnv *env, jclass clazz, ...){
//just an example
int x=8;
// restoring stack pointers
asm (
"movl %ebp, %esp;"
"mov %rbp, %rsp"
);
// add 4 to the function address to skip "push ebp / mov ebp esp"
asm volatile("jmp *%0;"::"r" (originalfunc+4));
// will not get here anyway
return NULL;
}
void* interceptor(JNIEnv *env, jclass clazz, ...){
// first when interceptor is called, probably some parameter restoring...
push %rbp
mov %rsp %rbp
sub $0x30, %rsp
mov %rcx, 0x10(%rbp)
mov %r8, 0x20(%rbp)
mov %r9, 0x28(%rbp)
mov %rdx, 0x18(%rbp)
// int x = 8;
movl $0x8, -0x4(%rbp)
// my inline asm restoring stack pointers
mov %ebp, %esp
mov %rbp, %rsp
// asm volatile("jmp *%0;"::"r" (originalfunc+4))
mov 0xa698b(%rip),%rax // store originalfunc in rax
add %0x4, %rax
jmpq *%rax
// return NULL;
mov $0x0, %eax
}
现在是printf变体的汇编输出...
void* interceptor(JNIEnv *env, jclass clazz, ...){
//just an example
int x=8;
printf("hey");
// restoring stack pointers
asm (
"movl %ebp, %esp;"
"mov %rbp, %rsp"
);
// add 4 to the function address to skip "push ebp / mov ebp esp"
asm volatile("jmp *%0;"::"r" (originalfunc+4));
// will not get here anyway
return NULL;
}
void* interceptor(JNIEnv *env, jclass clazz, ...){
// first when interceptor is called, probably some parameter restoring...
push %rbp
mov %rsp %rbp
sub $0x30, %rsp
mov %rcx, 0x10(%rbp)
mov %r8, 0x20(%rbp)
mov %r9, 0x28(%rbp)
mov %rdx, 0x18(%rbp)
// int x = 8;
movl $0x8, -0x4(%rbp)
// printf("hey");
lea 0x86970(%rip), %rcx // stores "hey" in rcx???
callq 0x6b701450 // calls the print function, i guess
// my inline asm restoring stack pointers
mov %ebp, %esp
mov %rbp, %rsp
// asm volatile("jmp *%0;"::"r" (originalfunc+4))
mov 0xa698b(%rip),%rax // store originalfunc in rax
add %0x4, %rax
jmpq *%rax
// return NULL;
mov $0x0, %eax
}
这里是 printf 函数的汇编代码:
printf(char const*, ...)
push %rbp
push %rbx
sub $0x38, %rsp
lea 0x80(%rsp), %rbp
mov %rdx, -0x28(%rbp)
mov $r8, -0x20(%rbp)
mov $r9, -0x18(%rbp)
mov $rcx, -0x30(%rbp)
lea -0x28(%rbp), %rax
mov %rax, -0x58(%rbp)
mov -0x58(%rbp), %rax
mov %rax, %rdx
mov -0x30(%rbp), %rcx
callq 0x6b70ff60 // (__mingw_vprintf)
mov %eax, %ebx
mov %ebx, %eax
add $0x38, %rsp
pop %rbx
pop %rbp
retq
看起来 printf 在 rbp 上执行了许多操作,但我没有发现任何问题...
这是被拦截函数的汇编代码。
push %rbp // 1 byte
push %rsp, %rbp // 3 bytes , need to skip them
sub $0x50, %rsp
mov %rcx, 0x10(%rbp)
mov %rdx, 0x18(%rbp)
mov %r8d, %ecx
mov %r9d, %edx
mov 0x30(%rbp), %eax
mov %cl, 0x20(%rbp)
mov %dl, 0x28(%rbp)
mov %ax, -0x24(%rbp)
************* EDIT 2 **************
我认为了解运行时内存如何变化很有用:
第一张图片显示进入拦截器函数后的内存布局:
第二张图片显示了同一内存区域在出现问题的代码(例如 printf 等)之后的情况。
第三张图片显示跳转到原始函数后的内存布局。
可以看到,在调用 printf 后,堆栈看起来很正常,但是当我跳转到原始函数时,它变得混乱了......
从这些截图中可以看出,所有参数都位于内存中的堆栈上,并且参数不是通过寄存器传递的。
push ebp / mov ebp esp
是3个字节长,而不是4个。 - manuell