网上有不少文章记述了如何在java程序中通过jni接口调用c/c++ native动态库,文章也的写不错,让我这个java外行人能一眼看懂。唯一可惜的就是没有关于如何调试native动态库的文章。既然如此,就由我来完成这任务。
分别贴出java和C++源码:
package compile;
public class compile
{
public native int Msg(String msg);
public static void main(String[] args)
{
System.loadLibrary("JniDll");
compile compileObj = new compile();
compileObj.Msg("Msg");
}
}
源码中声明了一个native方法int Msg(String msg);这个方法通过c++实现,并以dll的形式导出函数,源码如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class compile_compile */
//这段源码包含于compile_compile.h中,是javah生成的头文件
#ifndef _Included_compile_compile
#define _Included_compile_compile
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: compile_compile
* Method: Msg
* Signature: (Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_compile_compile_Msg
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
#include "compile_compile.h"
//这段源码包含于compile_compile.cpp中,用于实现Java_compile_compile_Msg接口,并最终生成jniDll.dll文件
JNIEXPORT jint JNICALL Java_compile_compile_Msg
(JNIEnv* javaEnv, jobject javaObj, jstring javaStr)
{
const char* msgStr = javaEnv->GetStringUTFChars(javaStr,0);
MessageBox(NULL,msgStr,"",MB_OK);
return 0;
}
继续开始之前,容我解释一下源码的用意:java程序先加载jniDll.dll然后调用其中的导出函数Java_compile_compile_Msg。这个导出函数的功能很简单,仅仅是弹出一个对话框。我要做的是当java解释器加载动态库jniDll.dll时,中断调试器到交互状态,使得程序员有机会为动态库中的其他源码下断点调试。
编译生成相应的类文件和dll后,在用java解释器加载运行compile类前,必须明确被调试的目标进程。在命令行中输入命令后
C:\Users\Administrator\Desktop\studio\eclipse\compile>java -classpath ./bin;"%CL
ASSPATH%" compile.compile
cmd.exe的控制权交给java.exe,直到java.exe运行结束,才会响应新的控制台输入(如图)。
另外,compile类和jniDll.dll都不能单独成为进程运行,他们是作为java.exe的一部分被加载运行。(上图中,点击任务栏的java图标后显示jniDll.dll中的MessageBox也是一个很好的辅证)。综上所述,我们的调试目标进程是java.exe。
调试进程确定后剩下的简单了,打开windbg-File-"Open Executable",进入Open Executable对话框:
在"文件名"框中输入java.exe的全路径,
"Arguments"框输入java.exe运行compile类时需要参数,本文中是:-classpath ./bin;"%CLASSPATH%" compile.compile
"Start directory"框输入compile工程的路径,我的工程(由eclipse创建)为与C:\Users\Administrator\Desktop\studio\eclipse\compile下。
最后点击打开,运行java.exe
程序运行后马上会遇到初始断点,并中断到调试器。
在调试器中使能模块加载断点:
0:000> sxe ld jniDll
0:000> g #继续运行程序
继续运行程序后,调试器会因为加载jniDll事件再次被中断,此时,我们可以为windbg添加jniDll.dll的调试符号路径(生成jniDll.dll时一起生成的jniDll.pdb所在目录的路径)并下断点:
0:000:x86> .symfix C:\symbols\this #设置调试符号路径
0:000:x86> .sympath+ C:\studio\JniDll\Debug
Symbol search path is: srv*;C:\studio\JniDll\Debug
Expanded Symbol search path is: SRV*C:\symbols\this*http://msdl.microsoft.com/download/symbols;c:\studio\jnidll\debug
0:000:x86> .reload
Reloading current modules
............................................
*** ERROR: Module load completed but symbols could not be loaded for java.exe
0:001:x86> .srcpath C:\studio\JniDll #设置jniDll源码路径
0:000:x86> x USER32!*MessageBox* #准备在MessageBox函数下断点
758620a6 USER32!MessageBoxA = <no type information>
75864551 USER32!MessageBoxW = <no type information>
0:000:x86> bp USER32!MessageBoxA
0:000:x86> bp USER32!MessageBoxW
0:000:x86> g
*** WARNING: Unable to verify checksum for C:\Users\Administrator\Desktop\studio\eclipse\compile\JniDll.dll
ModLoad: 00000000`57d70000 00000000`57d8b000 C:\Users\Administrator\Desktop\studio\eclipse\compile\JniDll.dll #java.exe加载jniDll,触发了模块加载事件
ntdll!ZwMapViewOfSection+0xa:
00007ffe`db5e67fa c3 ret
0:001> g
Breakpoint 0 hit #遇到前面设置的断点
USER32!MessageBoxA:
758620a6 8bff mov edi,edi
经过这一轮设置,我们终于可以像调试普通程序一样调试jniDll了~
0:001:x86> kb #查看函数调用堆栈
ChildEBP RetAddr Args to Child
0076f918 57d813b4 00000000 14ad8040 57d8553c USER32!MessageBoxA
0076fa08 024ec0c3 02129140 0076fa60 0076fa5c JniDll!Java_compile_compile_Msg+0x44 [c:\studio\jnidll\jnidll\jnidll.cpp @ 11] #调用Msg导出函数的源码函数<--
WARNING: Frame IP not in any known module. Following frames may be wrong.
0076faa8 57248bf5 0076fadc 0076fc9c 0000000a 0x24ec0c3
0076fb30 5730e0be 0076fc94 0076fba4 0076fc14 jvm!JVM_GetThreadStateNames+0x4cd75
0076fb78 57248c8e 57248a20 0076fc94 0076fba4 jvm!JVM_FindSignal+0x635ce
0076fb98 571caca7 0076fc94 14830238 02129000 jvm!JVM_GetThreadStateNames+0x4ce0e
0076fc5c 571d323f 02129140 14b759a8 14b759a8 jvm!JNI_GetCreatedJavaVMs+0x6ea7
0076fcb4 0019228d 02129140 0212a214 14b759a8 jvm!JNI_GetCreatedJavaVMs+0xf43f
0076fd00 0019ae9f 02129140 6ebad725 00000000 java+0x228d
0076fd38 0019af29 00000000 0076fd50 75cb495d java+0xae9f
0076fd44 75cb495d 02132ec0 0076fd94 77ae98ee java+0xaf29
0076fd50 77ae98ee 02132ec0 19186b7e 00000000 KERNEL32!BaseThreadInitThunk+0xe
0076fd94 77ae98c4 ffffffff 77ade0fd 00000000 ntdll_77aa0000!__RtlUserThreadStart+0x20
0076fda4 00000000 0019aec5 02132ec0 00000000 ntdll_77aa0000!_RtlUserThreadStart+0x1b