主要流程:
1.使用jdb启动生成的java字节码或jar包;
2.在java源码中适当的位置设置断点,如main方法;
3.使用run指令,使代码跑起来。(jdb命令只是进入调试环境,jvm还没有运行起来,当然我们的代码也没有运行起来,run指令之后,才有创建一个jvm,在进程列表中会发现多出一个java进程。)
4.另起一个终端,使用查询新生成的java进程的进程id。
5.使用gdb命令附着到上述java进程。
6.在C源码中适当的位置设置断点。
至此,两个调试都挂在了同一个jvm上。需要注意,只有两个调试器同时允许程序执行是,程序才会继续运行。
以HelloWorld为例说明:
第一至第三步如下:
$jdb HelloWorld <= 第一步
正在初始化jdb...
>stop in HelloWorld.main <= 第二步,设置适当的断点
正在延迟断点HelloWorld.main。
将在加载类后设置。
>run <= 第三步, run起来
运行HelloWorld
设置未捕获的java.lang.Throwable
设置延迟的未捕获的java.lang.Throwable
>
VM已启动:设置延迟的断点HelloWorld.main
断点命中:"线程=main",HelloWorld.main(), 行=4bci=0
4 HelloWorld hw = new HelloWorld();
main[1]
然后另起一个终端,继续第四步至第六步
$ps -e | grep java
32160pts/24 00:00:00 java
$sudo gdb -p 32160
GNUgdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1
Copyright(C) 2014 Free Software Foundation, Inc.
//××××此处有打断输出,省略
Loadedsymbols for /usr/lib/jvm/jdk1.7.0_07/jre/lib/i386/libdt_socket.so
Readingsymbols from/home/luyao/java/jni/HelloWorld/libHelloWorld.so...done.
Loadedsymbols for /home/luyao/java/jni/HelloWorld/libHelloWorld.so <= 可以看到已经加载我们的库了
0xb775f428in __kernel_vsyscall ()
(gdb)break HelloWorld.c:Java_HelloWorld_print <=设置一个断点,在native的print函数前
Breakpoint1 at 0xb682d58d: file HelloWorld.c, line 8.
(gdb)c <= gdb释放阻塞。还需jdb调用cont命令,程序才会继续执行。
Continuing.
回到第一个终端,执行cont命令
第一个终端里:
main[1]cont
InJava, before> native
第二个终端里:
(gdb)c
Continuing.
[Switchingto Thread 0xb68edb40 (LWP 32164)]
Breakpoint1, Java_HelloWorld_print (jenv=0xb6707d28, jobj=0xb68ed010)
atHelloWorld.c:8
8 inti = 0; <= Nice! 程序已经断在gdb里了。
(gdb)//然后就可以任意发挥了。
几个问题:
1)在上述C代码中,有一句注释掉的代码fflush(stdout);如果没有该行代码,在jdb调试器中,输出会发生乱序(native函数中的输出被延迟甚至漏掉)。单独挂gdb调试器时,没有这种问题。估计跟JVM中实现native层标准输出设备缓存的方法有关。等进一步研究JVM的实习后再来关注这个问题。
2)由于java对线程id描述的方式不同,目前无法确定从java代码调用native代码时是否发生线程切换,很大程度上感觉不会发生线程切换。在《Insidethe Java VirtualMachine》一书中只讲到从java调用native时只发生了线程栈的切换,即native代码有独立的栈,(这类似与用户态栈和内核态栈的区别)。没有线程的切换。但是这本书的编写时间较早,2000年,当时java应该还是1.3或者1.4,不知道在1.7,或者说在安卓的Dalvik虚拟机中的实现是否有不同,需进一步研究。
3)一个很困惑的问题。在启动gdb时,使用的时root权限,如果不用root权限,会报错,如下:
Attachingto process 7093
Couldnot attach to process. If your uid matches the uid of the target
process,check the setting of /proc/sys/kernel/yama/ptrace_scope, or try
againas the root user. For more details, see/etc/sysctl.d/10-ptrace.conf
ptrace:不允许的操作.
不明白的是,非 root 下,我的 uid 肯定是一致的,即目标进程 java 的 uid 也是当前用户。而使用 root 权限启动 gdb 时, gdb 的 uid 显然与目标进程 java 的 uid 不一致了
$ps -aux | grep java
luyao 7093 0.0 0.6 688660 13812 pts/24 tl+ 00:14 0:00/usr/lib/jvm/jdk1.7.0_07/jre/bin/java -Xdebug-Xrunjdwp:transport=dt_socket,address=luyao-K40IN:55455,suspend=yHelloWorld
luyao 8765 0.0 0.0 6116 824 pts/7 S+ 00:20 0:00 grep--color=auto java
luyao@luyao-K40IN:~/java/jni/HelloWorld$ps -aux | grep gdb
root 8611 0.0 0.0 8312 2036 pts/12 S+ 00:19 0:00 sudo gdb-p 7093
root 8612 0.8 1.4 40216 30012 pts/12 S+ 00:19 0:00 gdb -p7093
luyao 8857 0.0 0.0 6116 824 pts/7 S+ 00:20 0:00 grep--color=auto gdb
查看上述错误提示中提到的文档如下:
$cat /etc/sysctl.d/10-ptrace.conf
#The PTRACE system is used for debugging. With it, a single userprocess
#can attach to any other dumpable process owned by the same user. Inthe
#case of malicious software, it is possible to use PTRACE to access
#credentials that exist in memory (re-using existing SSH connections,
#extracting GPG agent information, etc).
#
#A PTRACE scope of "0" is the more permissive mode. A scopeof "1" limits
#PTRACE only to direct child processes (e.g. "gdbname-of-program" and
#"strace -f name-of-program" work, but gdb's "attach"and "strace -fp $PID"
#do not). The PTRACE scope is ignored when a user has CAP_SYS_PTRACE,so
#"sudo strace -fp $PID" will work as before. For moredetails see:
#https://wiki.ubuntu.com/SecurityTeam/Roadmap/KernelHardening#ptrace
#
#For applications launching crash handlers that need PTRACE,exceptions can
#be registered by the debugee by declaring in the segfault handler
#specifically which process will be using PTRACE on the debugee:
# prctl(PR_SET_PTRACER, debugger_pid, 0, 0, 0);
#
#In general, PTRACE is not needed for the average running Ubuntusystem.
#To that end, the default is to set the PTRACE scope to "1". This value
#may not be appropriate for developers or servers with only adminaccounts.
kernel.yama.ptrace_scope= 1
$cat /proc/sys/kernel//yama/ptrace_scope
1
好吧,应该是ubuntu自身的安全机制导致, 将 /proc/sys/kernel//yama/ptrace_scope 中的值改为 0 就可以了。