|
<script type="text/javascript"></script> <noscript></noscript> <script type="text/javascript"></script> <noscript></noscript>
| <!----><!----><!---->
|
级别: 初级 张 垚 (yaozhang@cn.ibm.com), 软件工程师, IBM 2008 年 4 月 17 日
在当今的应用程序开发尤其是大型程序开发中,混合语言的使用已经是相当普 遍了。在 Java 应用程序开发中,程序员可以很方便的使用 JNI(Java Native Interface) 来实现 C/C++ 和 Java 代码的相互调用。本文简要介绍了在 Linux/Macintosh 平台上针对 JNI 的混合语言调试环境的搭建以及调试方法,并通过一个调试实例来给读者提供一个比较直观的演示。
<!----><!----><!----> 简介 在当今的应用程序开发尤其是大型程序开发中,混合语言的使用已经是相当普遍了。在 Java 应用程序开发中,程序员可以很方便的使用 JNI(Java Native Interface) 来实现 C/C++ 和 Java 代码的相互调用。在一些大型 C/C++ 项目向 Java 的迁移过程中,将一部分代码用 Java 重写,并使用 JNI 来保持功能性是一种采用较多的可控性比较强的设计。 本文简要介绍了在 Linux/Macintosh 平台上针对 JNI 的混合语言调试环境的搭建以及调试方法,并通过一个调试实例来给读者提供一个比较直观的演示。 在 Linux/Macintosh 平台上, Eclipse JDT 是最好的 Java 开发集成环境之一。读者如果想了解更多 JDT 相关信息,可以去 http://www.eclipse.org/jdt/ 看一下,本文只会介绍和调试相关的内容。
JNI 混合语言调试 Linux/Macintosh 平台下 Eclipse 环境中的 JNI 混合语言调试过程分为两部分,第一部分是在 Eclipse JDT 环境中对 Java 代码的调试,第二部分则是当程序运行到 C/C++ 代码部分是用 gdb 对 C/C++ 代码的调试。
在 Eclipse 集成环境中调试 Java 代码 在 Eclipse 中启动 Java 程序并进行调试,主要有两种方式:本地调试和远程调试。本地调试就是直接从本机的 Eclipse 环境中启动 Java 程序进行调试。远程调试是 attach 到网络远程机器上运行的 java 程序,并进行调试。这两种调试方法有各自的适用范围。 本地调试一般适用于程序员的开发环境,并且调试内容不依赖与 UI 事件。本地调试的好处在于你可以完整的调试整个应用程序的生命周期,从一开始启动到程序退出。 远程调试则主要在两种情况下使用。1)被调试的 Java 程序所运行的机器上没有也无法安装 Eclipse, 比如客户机器上运行的产品。2)所调试的 Java 程序内容依赖于某些 UI 事件,比如焦点切换等等。远程调试必须在被调试的程序已经运行起来后,才能 attach 并进行调试,因此它无法完整的调试应用程序的整个生命周期。 本地调试 在开始调试之前,你需要做一些配置工作。
- 首先启动 Eclipse 并切换到 JDT 视图。
- 点击 Window 菜单。
- 点击 Open Perspective 菜单项。
- 在 Open Perspective 的子菜单中点击 Java 项。
图1 Open Perspective 的子菜单中的 Java 项
- 打开调试设置对话框。
- 点击 Run 菜单。
- 点击 Open Run Dialog 菜单项。
图2 Open Run Dialog 菜单项
- 根据调试的 Java 应用程序选择调试类型,常用的有:
- Eclipse Application
- Java Applet
- Java Application
图3 Java 应用程序选择调试类型
这里我们以 Java Application 为例。接下来你需要设置相关的程序启动参数。
- 首先需要设置 Main Class 相关参数,在 Debug 对话框中点击 Main 页面:
图4 Debug 对话框的 Main 页面
- 在 Project 一栏中填入你想要调试的项目名称,你可以通过点击 Browse… 按钮来在当前 workspace 中选择项目。
- 在 Main class 一栏中填入你要调试的Java Main class, 你可以点击 Search… 按钮来 在当前 Workspace 里所有实现 main 方法的 class 中进行搜索。
- 在 Arguments 页面里,你可以设置程序启动的相关参数以及 JVM 的参数。
图5 设置程序启动相关参数以及 JVM 参数
- 在 JRE, Classpath, Source, Environment 以及 Common 中还可以设置其他参数,这里就不详细叙述了,读者可以自行参考 Eclipse 帮助文档的 Java Development User Guide 部分。
- 调试启动参数设置完毕以后,就可以点击 Debug 按钮来启动程序进行调试了。
远程调试 远程调试的设置则相对简单。
- 远程机器上运行的被调试程序必须在启动时添加如下的JVM参数:
-Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n 这里有两点需要说明一下 1)address=8000 中的 8000 指的是端口号,你也可以指定别的未使用的端口号,调试机器需要在 Eclipse 中配置相同的端口号才能成功连接。 2)请注意参数的逗号前后是没有空格的。
- 在调试机器上重复本地调试步骤中的1至3步,并选择 Remote Java Application 这种调试类型。
图6 Remote Java Application 调试类型
- 在 Connect 页面的 Connection Properties 中,Host 里填入被调试程序所在机器的 IP 地址,在 Port 里填入第 1 步中所制定的端口号。
- 在 Source 页面里,你可以指定源文件的目录。在远程调试中,调试机器不需要有被调试程序的可执行文件,只要有源文件就可以了。当然,如果你没有源文件,也可以 attach 上去,你可以看到现场的调用堆栈,但是无法进行单步执行等动作。
调试 Java 代码 无论是本地调试还是远程调试,在进行调试后就可以通过点击菜单 Windows->Open Perspective->Debug 来切换到 Debug Perspective。 图7 Open Perspective 的 Debug 菜单项
Debug Perspective 中主要包括 Debug, Editor, Outline, Variable, Breakpoint 和 Console 视图。 图8 Debug Perspective
具体每个视图的使用和常用的调试命令这里就不一一详细叙述了,读者可以参考 Eclipse 帮助文档的 Java Development User Guide 中相关的视图部分文档。 JNI 程序的纯 Java 代码部分的调试是和调试其他 Java 程序相同的,所不同的地方就是当调用到 native 方法的时候,再往里执行就会进入 C/C++ 代码,这时候就需要使用 gdb 来进行调试了,在下一节中会详细介绍。 Tips 在这里作者有几个在日常调试 Java 代码过程中总结的 Tips 和大家分享一下。
在 Eclipse 中显示的调用堆栈只包括 Java 的堆栈,C/C++ 的堆栈是看不见的。那么当你断在某个 native 方法的 C/C++ 实现中,却想要看到 Java 的调用堆栈,就可以通过在 Eclipse 中将断点设在 native 方法上,而不是某一代码行来达到目的。具体方法是在 Outline 视图或者 Editor 视图中选择欲设置断点的 native 方法,然后点击菜单 Run->Toggle Method Breakpoint 即可。当程序调用这个 native 方法是,会自动断在方法入口处。
在某些情况下,条件断点的使用可以极大的提高调试效率。在 Eclipse 中设置条件断点的方法是:在 Breakpoint 视图中选择某个断点,在右键菜单中选择 Breakpoint Properties…,这时会弹出断点属性对话框。 在属性对话框中勾上 Enable Condition 项,就可以在文本框中输入条件表达式了。同时你还可以选择是当条件表达式为真是中断还是条件表达式的值变化的时候中断,以及是中断当前线程还是 JVM 中的所有线程。 图9 条件断点设置
在某些情况下,需要在程序抛出异常的时候及时中断,来获取异常发生时的现场。这可以通过设置异常断点来实现。 点击菜单 Run->Add Java Exception Breakpoint…,会显示异常断点设置对话框。 图10 异常断点设置
输入设置的异常类型,点击 OK 按钮,异常断点就设置好了。
在终端中使用 Gdb 调试 C/C++ 代码 当 Java 代码执行到调用 native 方法的时候,再往下执行就会进入 native 方法的 C/C++ 实现了。这时我们需要使用 gdb 来调试 native 方法的 C/C++ 实现代码。
- 打开一个 Shell 窗口。如果是远程调试,你需要用 Shell 远程登录到被调试程序所在的机器上。
- 使用 ps 命令来得到被调试程序(java)的进程 id。
图11 获取被调试程序(java)的进程 id
- 然后运行如下命令来使用 gdb attach 到被调试的 java 进程上。
图12 用 gdb attach 到被调试的 java 进程
- Attach 完毕后,你就可以像调试其他 C/C++ 程序那样调试 JNI native 方法的 C/C++ 代码了。
请注意,当你用 gdb attached 到 java 进程的时候, Eclipse 中的 Debug 视图会失去响应,此时你无法看到变量值或继续程序等等。直到你在 gdb 中使用 continue 命令继续程序执行时, Eclipse 中的 Debug 视图才会恢复响应。
一个调试实例 这一节将给读者具体演示一个 JNI 的本地调试实例。例子的运行环境是 Mac OSX 10.5, 代码非常简单,共包含两个文件 testrun.java 和 testjni.c。 代码1 testrun.java
package com.ibm.jni.test;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class testrun {
public static void main(String[] args) {
testrun tr = new testrun();
Display display = Display.getDefault();
Shell shell = new Shell(display);
System.loadLibrary(“jnitest”);
shell.open();
int a = tr.jniexample();
System.out.println(a);
while ( !shell.isDiposed()){
if ( !display.readAndDispatch() )
display.sleep();
}
}
public native int jniexample();
}
|
代码2 jnitest.c
#include <jni.h>
#include <stdio.h>
#include <Carbon/Carbon.h>
JNIEXPORT jint JNICALL Java_com_ibm_jni_test_testrun_jniexample( JNIEnv *env, jclass that)
{
printf(“this is in jni call\n”);
return -1231;
}
|
testrun.java 是 java 主程序,其代码就是 load 由 testjni.c 生成的 libjnitest.jnilib 并调用其 jniexample 方法。 jnitest.c 则包含了 com.ibm.jni.test.testrun.jniexample 的 C 实现。调试实例步骤如下:
- 在 testrun.java 中设置断点,不妨设置在 testrun.java 的12行。
- 从 Eclipse中启动 testrun.java, 程序会断在步骤1中设置的断点上,并自动切换到 Debug Perspective。
图13 切换到 Debug Perspective
你可以查看当前的调用堆栈,线程,断点,变量值等等。
- 点击 Step Over 按钮或按 F6 键,程序会单步向前执行一条语句。
- 在 JNI C 代码中设置断点。打开一个 Shell 窗口,键入 ps –ax|grep java。
图14 获取 Java 进程 id
- 键入 gdb –pid=90538,attach 到调试的 java 进程上。
- 键入 l testjni.c:7
- 键入 b testjni.c:7
- 键入 c
图15 Attach 到 java 进程并设置断点
C 代码中的断点已经设置完毕。
- 回到 Eclipse 界面中,点击 Resume 按钮或按 F8 键,继续 Java 程序运行。
- Eclipse 调试界面中会暂时失去响应,此时 gdb 已经断在我们前面设置的断点上。
图16 触发 gdb 断点
这时你可以像调试一个普通 C 程序那样进行调试。
- 键入 C,继续程序运行。
结束语 JNI 的混合语言调试是在编程过程中经常遇到的需求,在 Linux/Macintosh 平台上,Ecslipse 和 gdb 的混合使用功能强大而且使用方便。本文介绍了在 Linux/Macintosh 平台上针对 JNI 的混合语言调试环境的搭建以及调试方法,并结合一个具体实例演示了调试的基本过程,希望对读者有一点的帮助。 |