В моем проекте С++ 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;
}
Это все еще отлично работает, я могу выполнить некоторые базовые вычисления, затем reset указатель стека и перейти к моей исходной функции, исходная функция также правильно считывает параметры из var_args. Однако: если я заменяю базовые операции int на malloc
или printf("any string");
, то каким-то образом, если перейти в мою исходную функцию, тогда мои параметры перепутаются, а исходная функция закончит чтение неправильных значений...
Я попытался отладить это поведение, и я осмотрел регионы памяти, чтобы увидеть, что происходит неправильно... Прямо перед прыжком все выглядит нормально, за ними следуют функциональные параметры.
Если я прыжок без сложных вычислений, все работает нормально, область памяти за ebp не изменяется. оригинальная функция считывает правильные значения...
Теперь, если я скачок после выполнения printf (например), параметры, считанные исходным способом, будут повреждены...
Что вызывает это странное поведение? printf даже не сохраняет какие-либо локальные переменные в моем методе... Хорошо, что он хранит некоторые литералы в регистрах, но почему мой стек поврежден только после перехода и еще не до него?
Для этого проекта я использую компилятор g++ version 4.9.1, запущенный на машине Windows.
И да, я обеспокоен параметрами std:: forward и templates, но они просто не работают в моем случае... Aaand yes Я знаю, что переключение на другие методы немного хаки, но это единственная моя идея о том, как принести JNI-перехватчик для работы...
******************** EDIT ********************
Как обсуждалось, я добавляю сгенерированный код ассемблера с исходными функциями.
Функция без 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
}
И вот код asm для функции 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, но я не вижу ничего плохого в нем...
И вот код 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 **************
Я подумал, что было бы полезно посмотреть, как изменяется память во время выполнения:
На первом снимке отображается макет памяти сразу после ввода функции перехватчика:
На втором изображении показана такая же область памяти после проблемного кода (например, printf и т.д.)
На третьем рисунке показана схема памяти сразу после перехода к исходной функции.
Как вы можете видеть, сразу после вызова printf, стек выглядит отлично, однако, когда я перехожу в исходную функцию, это беспорядочно...
Посмотрев на скриншоты, я уверен, что все параметры лежат в стеке в памяти, а параметры не передаются регистрами.