JNI代码调试初体验
背景
在用Java调用C++的代码时,通过JNI方式时,遇到了未预期的情况,需要调试。
于是问题来了?Java的代码可以通过设置断点,在IDE中来调试。
但是C++的native方式怎么样调试呢?
JNI代码调试初体验
我这里的java代码是运行在服务器上的,但是IDE是在本机电脑上。
于是呢?
第一步,是通过jdwp的方式,在本机IDE上远程调度服务器上的Java代码。
第二步,是通过gdb,在服务器上进入Java进程。
具体如下:
JDWP远程调试Java代码
Java代码启动时,增加参数
-agentlib:jdwp=transport=dt_socket,server=y,address=8050,suspend=y
这样程序启动后,会等待IDE远程连接上后,再继续运行。
在eclipse中新建调度项目
Debug Configurations -> Remote Java Application -> New 来创建一个新的项目
在 Connection Properties中,在 Host 和 Port 中填写服务器的 IP 和 Port
启动 java 服务
先在 eclipse 中设置断点,然后启动程序。程序运行至断点处时,会中断。
gdb调试JNI代码
找到程序pid
jps
attach到pid
gdb -p ${pid}
attach到指定的进程
先通过jstack找到指定的nid,比如:
184 "main" #1 prio=5 os_prio=0 tid=0x0000ffff9404e000 nid=0xb336 at breakpoint[0x0000ffff9ac1e000]
185 java.lang.Thread.State: RUNNABLE
这里的nid为0xb336,即45878
print 0xb336
45878
然后在gdb中找到对应的thread
(gdb) info threads
Id Target Id Frame
- 74 Thread 0xffff9ac1f1e0 (LWP 45878) "java" 0x0000ffff9be5c008 in pthread_cond_wait@@GLIBC_2.17 () from /lib64/libpthread.so.0
这里找到了thread为45878,其Id为74,再切换到对应的thread线程
(gdb) thread 74
[Switching to thread 74 (Thread 0xffff9ac1f1e0 (LWP 45878))]
#0 0x0000ffff9be5c008 in pthread_cond_wait@@GLIBC_2.17 () from /lib64/libpthread.so.0
(gdb) bt
#0 0x0000ffff9be5c008 in pthread_cond_wait@@GLIBC_2.17 () from /lib64/libpthread.so.0
#1 0x0000ffff9b3db8f4 in os::PlatformEvent::park() () from /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.201.b09-2.el7_6.aarch64/jre/lib/aarch64/server/libjvm.so
ok,虽然到了这里了。但是是看不到变量的。所以我们还得尝试其它的办法。
再次体验
JNI库中加入调试代码
见从头开发一个自己的JavaCPP presets,具体为:
修改pom.xml,如下文所示,增加compilerOption配置。
<plugin>
<groupId>org.bytedeco</groupId>
<artifactId>javacpp</artifactId>
<configuration>
<compilerOptions>
<compilerOption>-O0</compilerOption>
<compilerOption>-g3</compilerOption>
</compilerOptions>
</configuration>
</plugin>
其实这样还是不够的,因为默认的-O3参数会把函数参数给优化掉。所以呢,还是用笨办法吧。
可以通过将javacpp.compiler.skip来设置成true,来跳过自动编译so包过程。
什么?那怎么手动编译so包?好吧,还是用自动编译so包为模板,来手动改改g++命令的参数吧。
直接用gdb运行java程序
gdb --args java ...
如果代码中没有用到过多的环境变量的话,这样是OK的。
否则的话,还是直接先启动一下java服务。再通过gdb attach到对应的pid上吧。
什么?还没有attach,服务已经报错退出了?
聪明的你自然可以猜到,可以在java服务中加一些sleep操作嘛。
通过阅读上面的g++中编译的cpp文件,找几个需要打断点的地方,然后在gdb中设置断点,比如:
break Java_org_bytedeco_javacpp_als_00024WordUnit_text
break Java_org_bytedeco_javacpp_als_00024Result_alternates__
break Java_org_bytedeco_javacpp_als_00024Phrase_words__
gdb常见命令
- 回车键:重复上一命令
- help (h):帮助
- run (r):重新开始运行
- start:运行程序,停在第一执行语句
- list (l):查看原代码,
- set: 设置变量的值
- next (n):单步向下
- step (s): 单步进入
- backtrace (bt): 查看调用栈帧和层级
- frame (f): 切换函数栈帧
- info (i):查看局部变量的数值
- finish: 结束当前函数,返回到函数调用点
- continue (c): 继续运行
- print (p): 打印
- quit (q): 退出gdb
- break +num: 在第num行设置断点
- info breakpoints: 查看设置的所有断点
- delete (d) breakpoints num: 删除第num个断点
- display: 追踪查看具体变量值
- undisplay:取消追踪观察变量
- watch:被设置观察点的变量发生修改时,打印显示
- i watch:显示观察点
- enable breakpoints:启用断点
- disable breakpoints:禁用断点
- x:查看内存x/20xw 显示20个单元,16进制,4字节每单元
- run argv[1] argv[2]:调试时命令行传参
- set follow-fork-mode child#Makefile项目管理:选择跟踪父子进程(fork())