zoukankan      html  css  js  c++  java
  • 远程调试方式

    使用特定JVM参数运行服务端代码

    要让远程服务器运行的代码支持远程调试,则启动的时候必须加上特定的JVM参数,这些参数是:

    1
    -Xdebug -Xrunjdwp:transport=dt_socket,suspend=n,server=y,address=${debug_port}

    其中的${debug_port}是用户自定义的,为debug端口,本例以5555端口为例。

    transport: 表示信息传递的连接方式, 其中,dt_socket是指用SOCKET模式,另有dt_shmem指用共享内存方式,其中,dt_shmem只适用于Windows平台。

    server: server=y 表示是监听其他debug client端的请求

    address 表示等待调试的连接网络端口

    suspend 表示是否在启动目标虚拟机后挂起虚拟机,如果要调试启动过程,请使用y

    参数说明:

    1. address为远程调试的端口号,目前我们服务器上8412是一个开放的端口(staging环境已验证)
    2. -Xrunjdwp  使用jdwp(Java Debug Wire Protocol)进行调试
    3. transport=dt_socket 使用socket方式进行连接,还可以使用其他通信方式如(dt_shmem 共享内存)
    4. suspend=n JVM监听address端口传来的信号时不挂起JVM中运行的进程

    示例方式:

    1. IntelliJ远程调试
    2. 使用JDI进行线上程序断点信息记录

    IntelliJ远程调试

    打开Intellij IDEA,在顶部靠右的地方选择”Edit Configurations…”,进去之后点击+号,选择”Remote”,按照下图的只是填写红框内的内容,其中host为远程代码运行的机器的ip/hostname,port为上一步指定的debug_port,本例是5555。然后点击Apply,最后点击OK即可

    启动debug模式

    现在在上一步选择”Edit Configurations…”的下拉框的位置选择上一步创建的remote的名字,然后点击右边的debug按钮(长的像臭虫那个),看控制台日志,如果出现类似“Connected to the target VM, address: ‘xx.xx.xx.xx:5555’, transport: ‘socket’”的字样,就表示连接成功过了。

    设置断点,开始调试

    远程debug模式已经开启,现在可以在需要调试的代码中打断点了,比如:

     

    如图中所示,如果断点内有√,则表示选取的断点正确。

    现在在本地发送一个到远程服务器的请求,看本地控制台的bug界面,划到debugger这个标签,可以看到当前远程服务的内部状态(各种变量)已经全部显示出来了,并且在刚才设置了断点的地方,也显示了该行的变量值。

     

    使用JDI进行线上程序断点信息记录

    java的整个调试体系为JDPA,Oracle提供了高级的jdi接口以方便使用java来连接调试程序进行相应的调试。这样,只需要调用相应的java接口,就能进行打断点,记录断点,然后继续运行,清除断点这样基本的断点调试手法了。

    import com.sun.jdi.*;
    import com.sun.jdi.connect.AttachingConnector;
    import com.sun.jdi.connect.Connector;
    import com.sun.jdi.event.*;
    import com.sun.jdi.request.BreakpointRequest;
    import com.sun.jdi.request.EventRequest;
    import com.sun.jdi.request.EventRequestManager;
    import com.sun.tools.jdi.SocketAttachingConnector;
    
    import java.util.List;
    import java.util.Map;
    
    /**
     * Created by nijianfeng on 16/10/24.
     */
    public class DebugTest {
    
        public static void main(String[] args) throws Exception {
            //获取SocketAttachingConnector,连接其它JVM称之为附加(attach)操作
            VirtualMachineManager vmm = Bootstrap.virtualMachineManager();
            List<AttachingConnector> connectors = vmm.attachingConnectors();
            SocketAttachingConnector sac = null;
            for(AttachingConnector ac : connectors) {
                if(ac instanceof SocketAttachingConnector) {
                    sac = (SocketAttachingConnector) ac;
                }
            }
            assert sac != null;
            //设置好主机地址,端口信息
            Map<String, Connector.Argument> arguments = sac.defaultArguments();
            Connector.Argument hostArg = arguments.get("hostname");
            Connector.Argument portArg = arguments.get("port");
            hostArg.setValue("127.0.0.1");
            portArg.setValue(String.valueOf(5555));
            //进行连接
            VirtualMachine vm = sac.attach(arguments);
            //相应的请求调用通过requestManager来完成
            EventRequestManager eventRequestManager = vm.eventRequestManager();
            //创建一个代码判断,因此需要获取相应的类,以及具体的断点位置,即相应的代码行。
            ClassType clazz = (ClassType) vm.classesByName("cn.gov.zcy.fixed.controller.base.AttachController").get(0);
            Location location = clazz.locationsOfLine(91).get(0);
            //创建新断点并设置阻塞模式为线程阻塞,即只有当前线程被阻塞。最终启用之。
            BreakpointRequest breakpointRequest = eventRequestManager.createBreakpointRequest(location);
            breakpointRequest.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
            breakpointRequest.enable();
            //获取vm的事件队列
            EventQueue eventQueue = vm.eventQueue();
            while(true) {
                //不断地读取事件并处理断点记录事件
                EventSet eventSet = eventQueue.remove();
                EventIterator eventIterator = eventSet.eventIterator();
                while(eventIterator.hasNext()) {
                    Event event = eventIterator.next();
                    execute(event);
                }
                //将相应线程resume,表示继续运行
                eventSet.resume();
            }
        }
        public static void execute(Event event) throws Exception {
            System.out.println(event.getClass().getCanonicalName());
            //获取的event为一个抽象的事件记录,可以通过类型判断转型为具体的事件,这里我们转型为BreakpointEvent,即断点记录,
            BreakpointEvent breakpointEvent = (BreakpointEvent) event;
            //并通过断点处的线程拿到线程帧,进而获取相应的变量信息,并打印记录。
            ThreadReference threadReference = breakpointEvent.thread();
            StackFrame stackFrame = threadReference.frame(0);
            List<LocalVariable> localVariables = stackFrame.visibleVariables();
            localVariables.forEach(t -> {
                Value value = stackFrame.getValue(t);
                System.out.println("local->" + value.type() + "," + value.getClass() + "," + value);
            });
    
    //        com.sun.tools.jdi.EventSetImpl.BreakpointEventImpl
    //        local->class java.lang.String (no class loader),class com.sun.tools.jdi.StringReferenceImpl,"1016MD/339900/039514a0-ea9d-449a-ba91-a6681acf30cd"
        }
    }

    连接远程JVM

    连接其它JVM称之为附加(attach)操作,当前实现中有2种,如果是本地JVM,则通过Process的方式即可如果是远程,则需要通过socket的方式才能进行连接。首先是server端需要开启调试agent,并且指定相应的端口,如下启动命令所示:

    -Xdebug -agentlib:jdwp=transport=dt_socket,address=1234,server=y,suspend=n

    上面的参数表示监听指定端口(1234),并且当存在断点时并不主动阻塞。旧的JVM也有使用Xrunjdwp参数的,但不再被建议使用.

    客户端即通过相应的connector进行连接,如下的socket连接方式:

    VirtualMachineManager vmm = Bootstrap.virtualMachineManager();
     
    List<AttachingConnector> connectors = vmm.attachingConnectors();
    SocketAttachingConnector sac = null;
    for(AttachingConnector ac : connectors) {
        if(ac instanceof SocketAttachingConnector) {
            sac = (SocketAttachingConnector) ac;
        }
    }
    assert sac != null;
     
    Map<String, Connector.Argument> arguments = sac.defaultArguments();
    Connector.Argument hostArg = arguments.get("hostname");
    Connector.Argument portArg = arguments.get("port");
     
    hostArg.setValue("127.0.0.1");
    portArg.setValue(String.valueOf(1234));
     
    vm = sac.attach(arguments);

    即设置好主机地址,端口信息,然后即可以进行连接。

    开启新断点

    相应的请求调用通过requestManager来完成,由刚才成功连接上的vm(VirtualMachine)来获取。由于这里是创建一个代码判断,因此需要获取相应的类,以及具体的断点位置,即相应的代码行。 

    要获取代码行,则要求源文件(class)中必须存在lineCode信息,即通过在抛出异常时,在异常信息中所指定的行数。这些信息默认情况下编译时会自动输出,但使用编译开关-g:none时,则不会输出。如果没有line信息,则不能创建相应的断点信息。创建信息如下所示:

    eventRequestManager = vm.eventRequestManager();
     
    ClassType clazz = (ClassType) vm.classesByName("A1").get(0);
    Location location = clazz.locationsOfLine(15).get(0);
     
    BreakpointRequest breakpointRequest
            = eventRequestManager.createBreakpointRequest(location);
    breakpointRequest.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
    breakpointRequest.enable();

    以上即拿到类A1的第15行的执行段(可能有多个,一行代码可能有多个调用),并设置阻塞模式为线程阻塞,即只有当前线程被阻塞。最终启用之,即成功创建新断点。这里必须将其设置为阻塞,否则将断点记录产生时,由于当前线程继续运行了,将不能拿到相应的信息(表示为产生IncompatibleThreadStateException)

    记录断点信息

    接下来就是从vm的事件队列中,不断地读取事件并处理断点记录事件即可。由于断点产生时,server端线程被阻塞,因此必须尽可能快地处理断相应的,以避免出现业务阻塞。标准的事件循环如下所示:

    eventQueue = vm.eventQueue();
    while(true) {
        eventSet = eventQueue.remove();
        EventIterator eventIterator = eventSet.eventIterator();
        while(eventIterator.hasNext()) {
            Event event = eventIterator.next();
            execute(event);
        }
     
        eventSet.resume();
    }

    上面在事件记录处理完之后,必须将相应线程resume,表示继续运行。

    这里获取的event为一个抽象的事件记录,可以通过类型判断转型为具体的事件,这里我们转型为BreakpointEvent,即断点记录,并通过断点处的线程拿到线程帧,进而获取相应的变量信息,并打印记录。如下所示:

    BreakpointEvent breakpointEvent = (BreakpointEvent) event;
    ThreadReference threadReference = breakpointEvent.thread();
    StackFrame stackFrame = threadReference.frame(0);
    List<LocalVariable> localVariables = stackFrame
            .visibleVariables();
    localVariables.forEach(t -> {
        Value value = stackFrame.getValue(t);
        System.out.println("local->" + value.type() + "," + value.getClass() + "," + value);
    });

    上面的value也是抽象形式,也可以通过类型转换以打印不同类型的对象。

    断点继续运行

    必须调用相应的event.resume

    清除断点

    如果记录的信息又足够(如记录了半天的数据了),则可能通过requestManager.deleteEventRequest调用相应的断点,或者调用deleteAllBreakpoints删除所有断点信息。或者通过vm.dispose直接中断相应的调试连接即可。

  • 相关阅读:
    教你如何去除电脑QQ聊天窗口上的广告?
    Web 通信技术 ——跨文档信息传输(JavaScript)
    JavaScript中如何给按钮设置隐藏与显示属性
    JavaScript中的innerHTML属性的使用
    JavaScript中点击按钮弹出新的浏览器窗口
    JavaScript中prompt的使用
    考试报名管理系统
    Python——用turtle模块画海龟的第一步
    Cardinality Estimation算法学习(二)(Linear Counting算法、最大似然估计(MLE))
    用KMP算法与Trie字典树实现屏蔽敏感词(UTF-8编码)
  • 原文地址:https://www.cnblogs.com/wade-luffy/p/5991785.html
Copyright © 2011-2022 走看看