C++跳转到其他方法执行

标签 c++ java-native-interface stack javaagents

在我的 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 读取参数。但是:如果我用 mallocprintf("any string"); 替换基本的 int 操作,那么,不知何故,如果跳转到我的原始函数,那么我的参数得到搞砸了,原始函数结束读取错误的值...

我已尝试调试此行为,并检查了内存区域以查看发生了什么问题...在跳转之前,一切看起来都很好,ebp 后面是函数参数。

如果我 在没有复杂计算的情况下跳转,一切正常,ebp 后面的内存区域不会改变。原始函数读取正确的值...

现在如果我在执行 printf 后跳转(例如),原始方法读取的参数会损坏...

是什么导致了这种奇怪的行为? printf 甚至没有在我的方法中存储任何 lokal 变量...好吧,它确实在寄存器中存储了一些文字,但是为什么我的堆栈仅在跳转之后才损坏而不是在它之前?

对于这个项目,我使用在 Windows 机器上运行的 g++ 版本 4.9.1 编译器。

是的,我担心 std::forward 和模板选项,但它们在我的情况下不起作用...... Aaand 是的,我知道跳入其他方法有点笨拙,但这是我唯一的想法如何带来JNI 拦截器工作...

********************编辑********************

如前所述,我将生成的汇编代码与源函数一起添加。

没有 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
}

现在 asm 输出 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 函数的 asm 代码:​​

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 做了很多操作,但我看不出有什么问题...

这里是截获函数的asm代码。

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)

****************** 编辑 2 **************

我认为在运行时查看内存如何变化会很有用:

第一张图是进入拦截器函数后的内存布局:

Memory Layout when entering the interceptor

第二张图片在有问题的代码(如 printf 等)之后显示了相同的内存区域

enter image description here

第三张图是跳转到原函数后的内存布局。

enter image description here

如您所见,调用 printf 后,堆栈看起来很好,但是当我跳转到原始函数时,它搞砸了......

看截图,我很确定所有参数都在内存中的堆栈上,并且参数不是通过寄存器传递的。

最佳答案

使用集合调用约定在汇编中手动传递参数。在这种情况下,参数在以 %rcx 开头的寄存器中传递。对用作调用约定的寄存器的任何修改都会改变任何进行的 jmp 所感知的参数。

jmp 之前调用 printf 会将 %rcx 的值从 *env 更改为指向常量的指针“你好”。更改 %rcx 的值后,您需要将其恢复为之前的值。以下代码应该可以工作:

void* interceptor(JNIEnv *env, jclass clazz, ...){

//just an example
int x=8;

printf("hey");

// restoring stack pointers
asm (
    "movl %ebp, %esp;"
    "mov %rbp, %rsp"
);

// restore %rcx to equal *env
asm volatile("mov %rcx, 0x10(%rbp)");

// 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;

}

关于C++跳转到其他方法执行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44117484/

相关文章:

c++ -::c++ 中模板函数调用前的作用域解析运算符

c++ - 我应该创建一个类对象吗?

c++ - 将 ISO C++ 类公开给 VB6 应用程序

java - 当我使用命令 java -jni HelloWorld 时遇到麻烦

android - 线程中的数学函数返回错误值,使用 android ndk

QT/QML Android 应用,点击通知栏打开应用

android - 是否可以在 Android 应用程序中通过 JNI 打印 C 代码控制台输出?

data-structures - 堆栈和队列,为什么?

从 Infix 转换为 Postfix 并评估 Postfix 表示法

python - 保存堆栈?