Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
vadimklimov
Active Contributor

Disclaimer


Material in this blog is provided for information and technical features demonstration purposes only. Described decompilation and debugging techniques, even in case they make perfect sense from technical perspective, shall be a subject for assessment and evaluation from legal perspective to avoid violation of software license agreements.

 

Intro


How often have you come across functionality in a SAP PI/PO system (SAP application server Java or SAP standard PI/PO applications, 3rd party vendor or in-house development) and needed to look into its implementation? There may be variety of reasons for that - root cause analysis of an issue, lack of available technical documentation and necessity to understand details of specific functionality realization are just few of them that come to my mind straightaway and that I personally face quite often.

 

engswee.yeoh wrote an excellent document regarding how to get yourself familiar with source code of PI adapter modules using standard functionality of NetWeaver Administrator and Java decompiler. Actually approach described by him goes beyond analysis of only adapter modules and is applicable to different other applications deployed on SAP application server Java. Some time ago I shared an approach how to find binary files that contain Java classes which we would like to look into - for example, in sake of further decompilation, by means of one of classloader utilities accessible via SAP AS Java Telnet (see How to Find Java Library Resource File Possessing Information for Class Name). These approaches are powerful and suitable if we would like to perform reverse engineering of a Java application by means of its decompilation and subsequent standalone analysis.

 

What if it is required not only to analyze source code, but also to observe its behaviour at runtime? Normally, the applicable approach is to use debugger and debug a Java application in question at runtime. This is relevant and convenient if it is possible to retrieve source code of a debugged application, import it to IDE of your choice and use remote Java application debugging features of that IDE. As I have written earlier, this is not always realistic and we may find ourselves in a situation where we need to debug a Java application for which we don't have source code or its acquisition (from original development project or decompiled bytecode) is time consuming / requires meaningful efforts. Let's have a look how to progress with it and achieve desired goal - namely, how to decompile and debug arbitrary Java application executed on SAP application server Java, on-the-fly.

 

It shall be noted that technique described in this blog, is helpful when analyzing non-obfuscated applications. Generally speaking, obfuscation is a process targeted at concealing original source code and making reverse engineering (e.g. based on decompilation) of an application complicated. In Java world, most commonly obfuscators change variables names, manipulate with class names, suppress end of line symbols, etc. - as a result, decompiled code differs a lot from its original source code version and is hardly readable. This is not a limitation relevant to a particular described technique or toolset - decompilation and analysis of obfuscated code is instrumentation agnostic complicating factor. Good news are, obfuscation is not very common technique for SAP PI/PO applications.

 

Another remark that shall be mentioned upfront, is that even in case of decompiling non-obfuscated application, decompiled version of generated source code may not have 100% match in comparison to original source code of an application and may have deviations. A reason for this is, that none of decompilers can produce identical replica of original source code - they are generating source code that will compile into identical binary / bytecode as original source code did. Various Java decompilers use different decompiling engines and algorithms, so even the same bytecode decompiled using different Java decompilers, may result in slightly different generated source code versions. Normally such discrepancies, in case of any, are not too significant and generated source code is helpful when debugging through it and assessing program flow.

 

Having all this in mind, let's move on to hands-on examples.

 

 

Preparation


First of all, we will need SAP JVM distribution. It can be downloaded from SAP Service Marketplace or retrieved from installed SAP application server Java based system. In examples below, I will use SAP JVM 6.1 (to be more precise, SAP JVM 6.1.085), but newer versions of SAP JVM (versions 7 and 😎 are also shipped with this kind of functionality.

 

SAP JVM distribution already contains Java debugger utilities (which are located in the same directory as other core binaries of SAP JVM - /bin/) - we will use one of them, jvmdebugger (SAP JVM Debugger), for debugging an application deployed on SAP PO system. Please note that in earlier patch levels of SAP JVM, jvmdebugger didn't exist - firstly, its alpha version was introduced - jvmdebugger-alpha. If you use older patch levels and don't find jvmdebugger, you may want to use jvmdebugger-alpha instead. In newer patch levels, jvmdebugger-alpha was deprecated and removed, and only jvmdebugger was left in distribution.

 

SAP JVM Debugger is a lightweight GUI application that provides major features relevant for Java debugger:



 

Before using SAP JVM Debugger, please ensure that debug mode is enabled on corresponding server nodes - this can be done for example using SAP Management Console, NWDS, NetWeaver Administrator or SAP JVM utility jvmmon:



SAP JVM supports "on-the-fly" debugging (debugging on-demand) - which means, debug mode can be switched on and off without need of system restart, as well as debugger can be connected and disconnected ad hoc. By default, SAP application server Java uses following formula to determine debug port: 5{instance number}20 + {server node index} * 5 + 1. For example, for server node 0 of instance number 00, default debug port is 50021. This can be changed if necessary. Please take a note of a host name (here, sap-po-poc) and debug port (here, 50021) of a debuggee (server node) - I will use them in later examples.

 

Next, in a clustered environment, it is highly recommended to isolate execution of calls that we want to debug, on specific server nodes. Since each server node of a cluster runs its individual JVM process, it is only possible to establish session between debugger and a specific server node (otherwise, it will be necessary to start several debugger instances and connect them to corresponding server nodes, which is not recommended due to increased load on a debuggee system). For some types of requests, this is possible (for example, HTTP requests can be sent to specific server node of a specific instance - see Addressing HTTP Requests to the Specific Server Node in AS Java 7.1 and Higher), but for some others this is not feasible.

 

Last, but not least, please ensure that there are no network security related issues that can prevent debugger from establishing connection to a debug port of a server node. One of most commonly faced issues is firewall settings that allow connections to only specific ports of SAP application server hosts or from only specific hosts.


Options to get connected to a debuggee


There are several options how SAP JVM Debugger can discover and connect to debuggee:

 

  • Connect manually using direct JDWP connection


The most straightforward and basic option is to connect to a debug port of a server node using direct JDWP connection. JDWP stands for Java Debug Wire Protocol, which is a TCP/IP based protocol used by Java debugger and debuggee to communicate to each other. This is a part of Java Platform Debugger Architecture (JPDA), which describes a standardized way for Java debugger and debuggee interoperability, most (if not all) modern Java IDEs and JVMs are compliant with it.

 

In SAP JVM Debugger, select Connect > Connect manually, and enable option "Direct JDWP Connection". Specify host name (or IP address) and debug port of debuggee (the debug port which is used by corresponding server node - see section above):



 

  • Connect manually via debugger daemon


This option is based on indirect communication between debugger and debuggee, which is mediated by debugger daemon, debugger daemon acts as a proxy. The option is useful if for some reason (for example, security limitations / firewall rules), it is not possible to establish direct connection from debugger to debuggee. Debugger daemon can be started on any host, to which debugger can connect, and from which debugger daemon can connect to debug port of debuggee - which also means, it is not necessary to start debugger daemon on the same host as SAP application server, but it can be started on another host and proxy actual location of a debuggee.

 

Debugger daemon can be started using utility jvmdebugger-daemon, which is a part of SAP JVM distribution. Mandatory argument is port number, which will be used by debugger daemon to listen for incoming calls from debugger. Optionally, it is also possible to specify debuggee details to which debugger daemon will forward debugger calls: basic option - host name / IP address and port, advanced option - SAP system details: instance number and server node VM identifier (in this case, even if debug mode was not enabled for server node explicitly, debugger daemon will enable it implicitly, but it then has to run on the same host as debuggee). If debuggee details are missing in arguments of debugger daemon, then it will be necessary to specify them in SAP JVM Debugger - that's why, to achieve complete transparency for SAP JVM Debugger not only from network connectivity perspective, but also from connectivity configuration perspective, it is advisable to maintain debuggee details as arguments when starting debugger daemon. On the other hand, if the same debugger daemon is going to be used to proxy debugger calls to several debuggees (for example, several server nodes or even nodes of several instances / systems), then it makes sense to start debugger daemon without debuggee details, and provide debuggee details when configuring connection in SAP JVM Debugger.

 

Debugging connection configuration screen is accessed in the same way, as in the previous option. Ensure that an option "Direct JDWP Connection" is disabled.

 

In examples below, debugger daemon was started on a host E107184, and a port 12345 was chosen as a listener port for it.

 

If debugger daemon is started with listener port and specific debuggee specification:



Then in SAP JVM Debugger, in debugging configuration, an option "Debuggee details already specified in jvmdebugger-daemon" has to be enabled, and only host name (or IP port) and listener port of debugger daemon are to be specified:



 

If debugger daemon is started with only listener port specification:



Then in SAP JVM Debugger, in debugging configuration, an option "Debuggee details already specified in jvmdebugger-daemon" has to be disabled, and host name (or IP port) and listener port of debugger daemon together with host name (or IP port) and debug port of debuggee are to be specified:



 

  • Discover available JVMs using JVM Browser and connect via jvmmond


This option allows not only to connect to a specific debuggee, but also perform its discovery at the beginning. The option is based on indirect communication between debugger and debuggee, which is mediated by jvmmond daemon.

 

Daemon can be started using utility jvmmond, which is a part of SAP JVM distribution, it shall be started on the same host as debuggee. There are no mandatory arguments for this utility. It is possible to specify port number, which will be used by daemon to listen for incoming calls from debugger - otherwise, it will used default port 1099.

 

In SAP JVM Debugger, select Connect > Connect using JVM browser, and specify host name (or IP address) of a host where jvmmond daemon is started. If non-default port is used, then host name (or IP address) and listener port shall be specified separated with colon. A a result, jvmmond daemon will show a list of discovered JVMs that are running on that host, and their properties:



After debuggee server node is found in a list, it can be selected and debug session can can started.

 

There are several advantages of using jvmmond daemon in comparison to other described options:

  • Debuggee discovery and debuggee details acquisition before establishing debug session - this is especially helpful if there are several server nodes running on the same host;

  • Transparency in accessing debuggee - no direct communication between debugger and debuggee;

  • Absence of necessity of explicit enabling debug mode on server node - even if debug mode is not enabled on an accessed server node, jvmmond implicitly enables it when it receives a call from debugger, and disables it after debugger is disconnected;

  • jvmmond daemon can handle and proxy not only debug calls, but also profiling requests (coming from Java profiler - another very important instrument in toolset of a developer and support team).


 

 

I personally prefer debugging / profiling server nodes via jvmmond whenever possible (due to reasons mentioned above), but if there is no possibility to start additional services (like debugger daemon or jvmmond) and debugger can establish connection to a debug port of debuggee, then direct connection via JDWP is also a valid option.

 

 

Debugging


After SAP JVM Debugger established debug session with debuggee using any of options described above, we will see layout that is very traditional to majority of Java debuggers, I will not go into details here.

 

Let me take execution of scenario with REST receiver communication channel as an example for debugging. From variety of options for setting breakpoints, I will set a breakpoint for method entry with default suspension type. In this example, method executeCall() of a class com.sap.aii.adapter.rest.ejb.receiver.RESTReceiverChannel was chosen, being one of critical classes in REST receiver logic execution.

 

Alternatively, I could have used other types of breakpoints - like breakpoint on method exit, breakpoint on specific code line, exception breakpoint, watchpoint, etc., depending on specific needs. Technically speaking, it could be literally any class and any method that is executed by server node - AS Java Engine functionality, PI Mapping Runtime, mapping programs, Adapter Framework, adapter applications, NWA and PIMON applications, or any other deployed applications.

 

I used thread suspension type (the default one). Another option would be to suspend the whole JVM when breakpoint is reached, which is helpful when dealing with multi-threaded applications to keep debugged runtime consistent. Breakpoint suspension type is configurable in properties of a breakpoint, accessible from context menu for a breakpoint.

 

Here is how the set enabled breakpoint looks:



After the breakpoint is set, I run the scenario so that the REST receiver communication channel is called and the breakpoint is reached - as a result, the thread that is executing the communication channel logic, is suspended, and we can analyze its call stack, explore class and instance variables content, step further in execution, etc. - generally speaking, perform common debugging activities as we would have done when doing Java application debugging:



 

 

Decompilation


Up to now, we are able to suspend debugged thread (or a complete JVM of a server node that we debug, if necessary), navigate through its call stack and variables, but we are still missing one important part - navigation through debugged code. This is now good time to decompile executed bytecode - and corresponding toolset was built-in SAP JVM Debugger. This is not a typo when I wrote "was" and not "is" - decompiler was built-in SAP JVM Debugger in earlier releases and patch levels of SAP JVM, but then it was intentionally removed from distribution.

 

In SAP JVM Debugger, while being connected to a debuggee and upon reaching a breakpoint and suspending thread / JVM, we can make an attempt to decompile bytecode of a given class by referencing it by its full quantified package and class name:



Following earlier example, I enter com.sap.aii.adapter.rest.ejb.receiver.RESTReceiverChannel here. Please note that decompiler doesn't automatically decompile class, which method is currently executed by a suspended thread. As a result, we have to manually instruct decompiler which class we want to decompile. This also means we are not limited to decompilation of purely the class that is currently executed by a thread - instead, we can decompile literally any valid class that is available for debuggee.

 

In newer versions of SAP JVM distribution, such decompilation attempt is likely to fail with the following error:



In earlier releases (for example, in SAP JVM Debugger alpha release), this functionality was not deprecated and removed, and would have resulted in on-the-fly decompilation of the specified class using built-in decompiler (owners of older SAP JVM releases may try this out). In newer releases, this was changed and the decompiler functionality was removed (causing the error message depicted on a screenshot above). Still there is a workaround that can be used to decompile and run through source code even in SAP JVM Debugger versions that don't have on-the-fly decompilation functionality. The idea of this workaround is to upload source code of corresponding classes into SAP JVM Debugger - decompilation, in its turn, is performed by external tools, and not those embedded into SAP JVM Debugger. Summary of required steps is as following:





  • Upload generated ZIP file into SAP JVM Debugger:



If classes that we need to decompile, are located in different binaries, it is also possible to specify a folder where all files with decompiled source code are located, or add several ZIP files:






  • Click on "Goto Source File" - and uploaded decompiled source code will be displayed. The decompiled code is mapped to executed bytecode - which means, proceeding in the program flow and going to next instructions will also take effect and highlight next executed instruction in a source code viewer:






 

As a result of actions described above, we end up with debug session which is connected to SAP application server Java and linked to decompiled source code, and we can now step further across debugged application at runtime and analyze details of its execution.

 

 

It shall also be mentioned here that SAP JVM Debugger can be used not only to run through decompiled source code, but has embedded disassembler and can be used to run through disassembled code. Good news are: we don't need to involve any external tools - disassembly of a class can be done using SAP JVM Debugger functionality. Even though majority of debugging cases don't require disassembly and the developer is focused on analysis of decompiled source code, there are some areas and use cases when decompilation is not relevant, and the way to proceed with analysis of a problem is to disassemble executed bytecode. For example, if subject of analysis is JVM core functionality or some lower level / native functions coming beyond boundaries of the executed application - analysis of class loading, Java locking and analysis of object monitor / lock acquisition / release attempts, calls of native functions (for example, interaction with network library or I/O system of a host) are some examples of that. This is a very powerful technique and tool, but its description deserves a separate discussion, so it is not in scope of this blog.

 

 

That's it for now. Good luck and happy debugging!
17 Comments
Labels in this area