Python API to support JIT

Jan Vrany jan@vrany.io
Fri May 13 11:41:13 GMT 2022


Hi there,

I just want to give you a heads-up on an experiment I'm (slowly) working 
on: a set of Python APIs to support JIT code.

As of now, we have extensive python API to have custom pretty
printers, frame decorators and frame unwinders - all these are essential 
when one wants good support for managed runtimes (such as JVM). JITed code 
often uses custom calling convention so custom unwinders are needed. Managed 
heap objects are quite often just untyped pointers to managed memory so one
need custom pretty printer to see "into" those objects.

There's a "JIT reader API" which allows one to load custom code into GDB which
then tells GDB that this range of memory belongs to function named "foo" and even
tell which PC corresponds to what line.

However, this JIT reader API it a C API so it requires one to implement that logic
in C. This means that you have half the support in written in C, other half in Python.
And I find it far more difficult to debug C-implemented JIT reader than any Python
script (such as pretty printer or unwinder).

So I decided to try to close this gap and extended Python API which can "teach" GDB
about dynamically compiled code [1]. As an example, I implemented an unwinder and JIT-code
registration code for OpenJ9 on RISC-V [2], [3].

As of now, it can do little more than (already existing) JIT reader API. Here's how code
registration looks in Python (keep in mind all this is just a prototype and demo, so I'm
cutting corners wherever I can :-)

---
def registerCompiled(self):
        """
        Register method in GDB.
        """

        # First. build GDB line table (don't confuse this with
        # Java line number table)
        lineTable = []
        pc = self.startPC
        while pc < self.endPC:
            line = self.lineNumberTable[self.bytecodeTable[pc]]
            lineTable.append(gdb.LineTableEntry(line, pc))
            pc = pc + 4

        self._objfile = gdb.Objfile(self.name)
        self._symtab = gdb.Symtab(self._objfile, self.className + '.java')
        self._symtab.set_linetable(lineTable)
        self._block = self._symtab.add_block(self.name, self.startPC, self.endPC)
---
This is called from a breakpoint on JIT compiler function that finalizes the compilation:

---
class MethodInfoRegistrar(gdb.Breakpoint):
    def __init__(self):
        super().__init__("TR::CompilationInfoPerThreadBase::logCompilationSuccess", internal=False)

    def stop(self):
        metaData = gdb.newest_frame().read_var('metaData')
        compiler = gdb.newest_frame().read_var('compiler')

        methodInfo = MethodInfo(metaData, compiler)
        methodInfo.registerCompiled()

        return True # Stop
---

With this in place, I can use standard gdb commands to set breakpoints, show backtrace,
source or disassembly:

(gdb) b 'MinimalTest.jit2jitFactorial(I)I'
Breakpoint 2 at 0x3f5b636028: file MinimalTest.java, line 115.

(gdb) c
Continuing.
+ (cold) MinimalTest.jit2jitFactorial(I)I @ 0000003F5B636024-0000003F5B6360B8 OrdinaryMethod - Q_SZ=0 Q_SZI=0 QW=2 j9m=0000003FF054FFE0 bcsz=16 sync
compThreadID=0 CpuLoad=101%(25%avg) JvmCpu=101%
[Switching to Thread 356032.356034]

Thread 2 "main" hit Breakpoint 2, 0x0000003f5b636028 in MinimalTest.jit2jitFactorial(I)I () at MinimalTest.java:115
115			if (i == 0) {

(gdb) bt 5
#0  0x0000003f5b636028 in MinimalTest.jit2jitFactorial(I)I () at MinimalTest.java:115
#1  0x0000003ff7cf88e2 in c_cInterpreter ()
    at /home/jv/Projects/J9/openj9-openjdk-jdk11-devscripts/openj9-openjdk-jdk11/build/linux-riscv64-normal-server-slowdebug/vm/runtime/vm/riscvcinterp.s:266
#2  0x0000003ff7beee6a in sidecarInvokeReflectMethodImpl (currentThread=0x3ff00b0400, methodRef=0x3ff00ff3c0,
    recevierRef=0x3ff00ff3b8, argsRef=0x3ff00ff3b0)
    at /home/jv/Projects/J9/openj9-openjdk-jdk11-devscripts/openj9-openjdk-jdk11/openj9/runtime/vm/callin.cpp:1209
#3  0x0000003ff7bef500 in sidecarInvokeReflectMethod (currentThread=0x3ff00b0400, methodRef=0x3ff00ff3c0,
    recevierRef=0x3ff00ff3b8, argsRef=0x3ff00ff3b0)
    at /home/jv/Projects/J9/openj9-openjdk-jdk11-devscripts/openj9-openjdk-jdk11/openj9/runtime/vm/callin.cpp:1353
#4  0x0000003ff67519e0 in JVM_InvokeMethod_Impl (env=0x3ff00b0400, method=0x3ff00ff3c0, obj=0x3ff00ff3b8,
    args=0x3ff00ff3b0)
    at /home/jv/Projects/J9/openj9-openjdk-jdk11-devscripts/openj9-openjdk-jdk11/openj9/runtime/sunvmi/sunvmi.c:363
(More stack frames follow...)

(gdb) disassemble /s 'MinimalTest.jit2jitFactorial(I)I'
Dump of assembler code for function MinimalTest.jit2jitFactorial(I)I:
MinimalTest.java:
115			if (i == 0) {
   0x0000003f5b636024 <+0>:	ld	a0,8(s11)
=> 0x0000003f5b636028 <+4>:	lw	a1,0(s11)
   0x0000003f5b63602c <+8>:	sd	ra,-8(s11)
   0x0000003f5b636030 <+12>:	addi	s11,s11,-48
   0x0000003f5b636034 <+16>:	ld	t3,80(s10)
   0x0000003f5b636038 <+20>:	blt	s11,t3,0x3f5b6360b4 <MinimalTest.jit2jitFactorial(I)I+144>
   0x0000003f5b63603c <+24>:	sd	s0,16(s11)
   0x0000003f5b636040 <+28>:	sd	s1,24(s11)
   0x0000003f5b636044 <+32>:	sd	a0,56(s11)
   0x0000003f5b636048 <+36>:	sw	a1,48(s11)
   0x0000003f5b63604c <+40>:	lw	s1,48(s11)
   0x0000003f5b636050 <+44>:	sext.w	s0,zero
   0x0000003f5b636054 <+48>:	bne	s1,s0,0x3f5b636070 <MinimalTest.jit2jitFactorial(I)I+76>

116				return 1;
   0x0000003f5b636058 <+52>:	addiw	a0,zero,1
   0x0000003f5b63605c <+56>:	ld	s0,16(s11)
   0x0000003f5b636060 <+60>:	ld	s1,24(s11)
   0x0000003f5b636064 <+64>:	addi	s11,s11,48
   0x0000003f5b636068 <+68>:	ld	ra,-8(s11)
   0x0000003f5b63606c <+72>:	ret

117			} else {
118				return jit2jitFactorial(i - 1) * i;
   0x0000003f5b636070 <+76>:	ld	s0,56(s11)
   0x0000003f5b636074 <+80>:	mv	a0,s0
   0x0000003f5b636078 <+84>:	lw	s1,48(s11)
   0x0000003f5b63607c <+88>:	addiw	a1,s1,-1
   0x0000003f5b636080 <+92>:	ld	s0,0(s0)
   0x0000003f5b636084 <+96>:	lui	t3,0x0
   0x0000003f5b636088 <+100>:	addi	t3,t3,-192
   0x0000003f5b63608c <+104>:	add	t3,s0,t3
   0x0000003f5b636090 <+108>:	ld	t3,0(t3)
   0x0000003f5b636094 <+112>:	jalr	t3
   0x0000003f5b636098 <+116>:	mv	s0,a0
   0x0000003f5b63609c <+120>:	mulw	a0,s1,s0
   0x0000003f5b6360a0 <+124>:	ld	s0,16(s11)
   0x0000003f5b6360a4 <+128>:	ld	s1,24(s11)
   0x0000003f5b6360a8 <+132>:	addi	s11,s11,48
   0x0000003f5b6360ac <+136>:	ld	ra,-8(s11)
   0x0000003f5b6360b0 <+140>:	ret
   0x0000003f5b6360b4 <+144>:	ebreak
End of assembler dump.

GDB/MI commands work as well so one can use GDB/MI frontend to debug JITed code.

As I said, this is still very much work in progress with many rough edges.
If anyone's interested, have questions or suggestions or working on the
same problem, please let me know! I'd welcome any feedback!

Best, Jan

[1]: https://sourceware.org/git/?p=binutils-gdb.git;a=shortlog;h=refs/heads/users/jv/wip/feature-py-jit-api
[2]: https://github.com/janvrany/openj9-gdb
[3]: https://github.com/janvrany/omr-gdb





More information about the Gdb mailing list