forked from Imagelibrary/binutils-gdb
gdb/python: add section in documentation on implementing JIT interface
This commit adds new section - JIT Interface in Python - outlining how to use Python APIs introduced in previous commits to implement simple JIT interface. It also adds new test to make sure the example code is up-to-date. Reviewed-By: Eli Zaretskii <eliz@gnu.org>
This commit is contained in:
4
gdb/NEWS
4
gdb/NEWS
@@ -96,6 +96,10 @@
|
||||
|
||||
** Added class gdb.Compunit.
|
||||
|
||||
** Extended the Python API to allow interfacing with JIT compilers using
|
||||
Python (as an alternative to JIT reader API). For details, see Section
|
||||
"JIT Interface in Python" in GDB documentation.
|
||||
|
||||
* Debugger Adapter Protocol changes
|
||||
|
||||
** The "scopes" request will now return a scope holding global
|
||||
|
||||
@@ -39886,7 +39886,8 @@ If you are using @value{GDBN} to debug a program that uses this interface, then
|
||||
it should work transparently so long as you have not stripped the binary. If
|
||||
you are developing a JIT compiler, then the interface is documented in the rest
|
||||
of this chapter. At this time, the only known client of this interface is the
|
||||
LLVM JIT.
|
||||
LLVM JIT. An alternative to interface descrived below is to implement JIT
|
||||
interface in Python (@pxref{JIT Interface in Python}).
|
||||
|
||||
Broadly speaking, the JIT interface mirrors the dynamic loader interface. The
|
||||
JIT compiler communicates with @value{GDBN} by writing data into a global
|
||||
|
||||
@@ -233,6 +233,7 @@ optional arguments while skipping others. Example:
|
||||
* Disassembly In Python:: Instruction Disassembly In Python
|
||||
* Missing Debug Info In Python:: Handle missing debug info from Python.
|
||||
* Missing Objfiles In Python:: Handle objfiles from Python.
|
||||
* JIT Interface in Python:: Writing JIT compilation interface in Python
|
||||
@end menu
|
||||
|
||||
@node Basic Python
|
||||
@@ -8554,6 +8555,127 @@ handlers, all of the matching handlers are enabled. The
|
||||
@code{enabled} field of each matching handler is set to @code{True}.
|
||||
@end table
|
||||
|
||||
@node JIT Interface in Python
|
||||
@subsubsection Writing JIT compilation interface in Python
|
||||
@cindex python, just-in-time compilation, JIT compilation interface
|
||||
|
||||
This section provides a high-level overview how to implement a JIT compiler
|
||||
interface entirely in Python. For alternative way of interfacing a JIT
|
||||
@pxref{JIT Interface}.
|
||||
|
||||
A JIT compiler interface usually needs to implement three elements:
|
||||
|
||||
@enumerate
|
||||
@item
|
||||
A way how to get notified when the JIT compiler compiles (and installs) new
|
||||
code and when existing code is discarded. Typical solution is to put a Python
|
||||
breakpoint (@pxref{Breakpoints In Python}) on some function known to be
|
||||
called by the JIT compiler once code is installed or discarded.
|
||||
|
||||
@item
|
||||
When a new code is installed the JIT interface needs to extract (debug)
|
||||
information for newly installed code from the JIT compiler
|
||||
(@pxref{Values From Inferior}) and build @value{GDBN}'s internal structures.
|
||||
@xref{Objfiles In Python}, @ref{Compunits In Python},
|
||||
@ref{Blocks In Python}, @ref{Symbol Tables In Python},
|
||||
@ref{Symbols In Python}, @ref{Line Tables In Python}).
|
||||
|
||||
@item
|
||||
Finally, when (previously installed) code is discarded the JIT interface
|
||||
needs to discard @value{GDBN}'s internal structures built in previous step.
|
||||
This is done by calling @code{unlink} on an objfile for that code
|
||||
(which was created in previous step).
|
||||
@end enumerate
|
||||
|
||||
Here's an example showing how to write a simple JIT interface in Python:
|
||||
|
||||
@c The copy of the code below is also in testsuite/gdb.python/py-jit.py
|
||||
@c and used by py-jit.exp to make sure it is up to date. If changed the
|
||||
@c test and py-jit.py should be checked and update accordingly if needed.
|
||||
@smallexample
|
||||
import gdb
|
||||
|
||||
class JITRegisterCode(gdb.Breakpoint):
|
||||
def stop(self):
|
||||
# Extract new code's address, size, name, linetable (and possibly
|
||||
# other useful information). How exactly to do so depends on JIT
|
||||
# compiler in question.
|
||||
#
|
||||
# In this example address, size and name get passed as parameters
|
||||
# to registration function.
|
||||
|
||||
frame = gdb.newest_frame()
|
||||
addr = int(frame.read_var('code'))
|
||||
size = int(frame.read_var('size'))
|
||||
name = frame.read_var('name').string()
|
||||
linetable_entries = get_linetable_entries(addr)
|
||||
|
||||
# Create objfile and compunit for allocated "jitted" code
|
||||
objfile = gdb.Objfile(name)
|
||||
compunit = gdb.Compunit(name, objfile, addr, addr + size)
|
||||
|
||||
# Mark the objfile as "jitted" code. This will be used later when
|
||||
# unregistering discarded code to check the objfile was indeed
|
||||
# created for "jitted" code.
|
||||
setattr(objfile, "is_jit_code", True)
|
||||
|
||||
# Create block for jitted function
|
||||
block = gdb.Block(compunit.static_block(), addr, addr + size)
|
||||
|
||||
# Create symbol table holding info about jitted function, ...
|
||||
symtab = gdb.Symtab("py-jit.c", compunit)
|
||||
linetable = gdb.LineTable(symtab, linetable_entries)
|
||||
|
||||
# ...type of the jitted function...
|
||||
void_t = gdb.selected_inferior().architecture().void_type()
|
||||
func_t = void_t.function(None)
|
||||
|
||||
# ...and symbol representing jitted function.
|
||||
symbol = gdb.Symbol(name, symtab, func_t,
|
||||
gdb.SYMBOL_FUNCTION_DOMAIN, gdb.SYMBOL_LOC_BLOCK,
|
||||
block)
|
||||
|
||||
# Finally, register the symbol in static block...
|
||||
compunit.static_block().add_symbol(symbol)
|
||||
|
||||
# ..and continue execution
|
||||
return False
|
||||
|
||||
# Create breakpoint to register new code
|
||||
JITRegisterCode("jit_register_code", internal=True)
|
||||
|
||||
|
||||
class JITUnregisterCode(gdb.Breakpoint):
|
||||
def stop(self):
|
||||
# Find out which code has been discarded. Again, how exactly to
|
||||
# do so depends on JIT compiler in question.
|
||||
#
|
||||
# In this example address of discarded code is passed as a
|
||||
# parameter.
|
||||
|
||||
frame = gdb.newest_frame()
|
||||
addr = int(frame.read_var('code'))
|
||||
|
||||
# Find objfile which was created in JITRegisterCode.stop() for
|
||||
# given jitted code.
|
||||
objfile = gdb.current_progspace().objfile_for_address(addr)
|
||||
if objfile is None:
|
||||
# No objfile for given addr (this should not normally happen)
|
||||
return False # Continue execution
|
||||
if not getattr(objfile, "is_jit_code", False):
|
||||
# Not a jitted code (this should not happen either)
|
||||
return False # Continue execution
|
||||
|
||||
# Remove the objfile and all debug info associated with it...
|
||||
objfile.unlink()
|
||||
|
||||
# ..and continue execution
|
||||
return False # Continue execution
|
||||
|
||||
# Create breakpoint to discard old code
|
||||
JITUnregisterCode("jit_unregister_code", internal=True)
|
||||
@end smallexample
|
||||
|
||||
@node Python Auto-loading
|
||||
@subsection Python Auto-loading
|
||||
@cindex Python auto-loading
|
||||
|
||||
61
gdb/testsuite/gdb.python/py-jit.c
Normal file
61
gdb/testsuite/gdb.python/py-jit.c
Normal file
@@ -0,0 +1,61 @@
|
||||
/* Copyright (C) 2024-2024 Free Software Foundation, Inc.
|
||||
|
||||
This file is part of GDB.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/mman.h>
|
||||
|
||||
/* "JIT-ed" function, with the prototype `long (long, long)`. */
|
||||
static const unsigned char jit_function_add_code[] = {
|
||||
0x48, 0x01, 0xfe, /* add %rdi,%rsi */
|
||||
0x48, 0x89, 0xf0, /* mov %rsi,%rax */
|
||||
0xc3, /* retq */
|
||||
};
|
||||
|
||||
/* Dummy function to inform the debugger a new code has been installed. */
|
||||
void jit_register_code (char * name, uintptr_t code, size_t size)
|
||||
{}
|
||||
|
||||
/* Dummy function to inform the debugger that code has been installed. */
|
||||
void jit_unregister_code (uintptr_t code)
|
||||
{}
|
||||
|
||||
int
|
||||
main (int argc, char **argv)
|
||||
{
|
||||
void *code = mmap (NULL, getpagesize (), PROT_WRITE | PROT_EXEC,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
assert (code != MAP_FAILED);
|
||||
|
||||
/* "Compile" jit_function_add. */
|
||||
memcpy (code, jit_function_add_code,
|
||||
sizeof (jit_function_add_code));
|
||||
jit_register_code ("jit_function_add", (uintptr_t)code, sizeof (jit_function_add_code));
|
||||
|
||||
((long (*)(long, long))code)(1,5); /* breakpoint 1 line */
|
||||
|
||||
/* "Discard" jit_function_add. */
|
||||
memset(code, 0, sizeof(jit_function_add_code));
|
||||
jit_unregister_code ((uintptr_t)code);
|
||||
|
||||
return 0; /* breakpoint 2 line */
|
||||
}
|
||||
57
gdb/testsuite/gdb.python/py-jit.exp
Normal file
57
gdb/testsuite/gdb.python/py-jit.exp
Normal file
@@ -0,0 +1,57 @@
|
||||
# Copyright (C) 2024-2024 Free Software Foundation, Inc.
|
||||
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# This file is part of the GDB testsuite. It test the Python API to
|
||||
# create symbol tables for dynamic (JIT) code and follows the example
|
||||
# code given in documentation (see section JIT Interface in Python)
|
||||
|
||||
load_lib gdb-python.exp
|
||||
|
||||
require allow_python_tests
|
||||
require is_x86_64_m64_target
|
||||
|
||||
standard_testfile
|
||||
|
||||
if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
|
||||
return -1
|
||||
}
|
||||
|
||||
if ![runto_main] {
|
||||
return 0
|
||||
}
|
||||
|
||||
set remote_python_file [gdb_remote_download host \
|
||||
${srcdir}/${subdir}/${testfile}.py]
|
||||
gdb_test_no_output "source ${remote_python_file}" "load python file"
|
||||
|
||||
gdb_breakpoint [gdb_get_line_number "breakpoint 1 line" ${testfile}.c]
|
||||
gdb_continue_to_breakpoint "continue to breakpoint 1 line"
|
||||
gdb_test "disassemble /s jit_function_add" \
|
||||
"Dump of assembler code for function jit_function_add:.*End of assembler dump." \
|
||||
"disassemble jit_function_add"
|
||||
|
||||
gdb_breakpoint "jit_function_add"
|
||||
gdb_continue_to_breakpoint "continue to jit_function_add"
|
||||
|
||||
gdb_test "bt 1" \
|
||||
"#0 jit_function_add \\(\\) at py-jit.c:.*" \
|
||||
"bt 1"
|
||||
|
||||
gdb_breakpoint [gdb_get_line_number "breakpoint 2 line" ${testfile}.c]
|
||||
gdb_continue_to_breakpoint "continue to breakpoint 2 line"
|
||||
|
||||
gdb_test "disassemble jit_function_add" \
|
||||
"No symbol \"jit_function_add\" in current context." \
|
||||
"disassemble jit_function_add after code has been unregistered"
|
||||
110
gdb/testsuite/gdb.python/py-jit.py
Normal file
110
gdb/testsuite/gdb.python/py-jit.py
Normal file
@@ -0,0 +1,110 @@
|
||||
# Copyright (C) 2024-2024 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is part of GDB.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
# This code is same (modulo small tweaks) as the code in documentation,
|
||||
# section "JIT Interface in Python". If changed the documentation should
|
||||
# be checked and updated accordingly if necessary.
|
||||
import gdb
|
||||
|
||||
objfile = None
|
||||
compunit = None
|
||||
block = None
|
||||
symtab = None
|
||||
symbol = None
|
||||
|
||||
def get_linetable_entries(addr):
|
||||
# Entries are not in increasing order to test that
|
||||
# gdb.LineTable.__init__() sorts them properly.
|
||||
return [
|
||||
gdb.LineTableEntry(31, addr+6, True),
|
||||
gdb.LineTableEntry(29, addr, True),
|
||||
gdb.LineTableEntry(30, addr+3, True)
|
||||
]
|
||||
|
||||
|
||||
class JITRegisterCode(gdb.Breakpoint):
|
||||
def stop(self):
|
||||
|
||||
global objfile
|
||||
global compunit
|
||||
global block
|
||||
global symtab
|
||||
global symbol
|
||||
|
||||
frame = gdb.newest_frame()
|
||||
name = frame.read_var('name').string()
|
||||
addr = int(frame.read_var('code'))
|
||||
size = int(frame.read_var('size'))
|
||||
linetable_entries = get_linetable_entries(addr)
|
||||
|
||||
# Create objfile and compunit for allocated "jit" code
|
||||
objfile = gdb.Objfile(name)
|
||||
compunit = gdb.Compunit(name, objfile, addr, addr + size)
|
||||
|
||||
# Mark the objfile as "jitted code". This will be used later when
|
||||
# unregistering discarded code to check the objfile was indeed
|
||||
# created for jitted code.
|
||||
setattr(objfile, "is_jit_code", True)
|
||||
|
||||
# Create block for jitted function
|
||||
block = gdb.Block(compunit.static_block(), addr, addr + size)
|
||||
|
||||
# Create symbol table holding info about jitted function, ...
|
||||
symtab = gdb.Symtab("py-jit.c", compunit)
|
||||
linetable = gdb.LineTable(symtab, linetable_entries)
|
||||
|
||||
# ...type of the jitted function...
|
||||
int64_t = gdb.selected_inferior().architecture().integer_type(64)
|
||||
func_t = int64_t.function(int64_t, int64_t)
|
||||
|
||||
# ...and symbol representing jitted function.
|
||||
symbol = gdb.Symbol(name, symtab, func_t,
|
||||
gdb.SYMBOL_FUNCTION_DOMAIN, gdb.SYMBOL_LOC_BLOCK,
|
||||
block)
|
||||
|
||||
# Finally, register the symbol in static block
|
||||
compunit.static_block().add_symbol(symbol)
|
||||
|
||||
return False # Continue execution
|
||||
|
||||
# Create breakpoint to register new code
|
||||
JITRegisterCode("jit_register_code", internal=True)
|
||||
|
||||
|
||||
class JITUnregisterCode(gdb.Breakpoint):
|
||||
def stop(self):
|
||||
frame = gdb.newest_frame()
|
||||
addr = int(frame.read_var('code'))
|
||||
|
||||
objfile = gdb.current_progspace().objfile_for_address(addr)
|
||||
if objfile is None:
|
||||
# No objfile for given addr - bail out (this should not happen)
|
||||
assert False
|
||||
return False # Continue execution
|
||||
|
||||
if not getattr(objfile, "is_jit_code", False):
|
||||
# Not a jitted addr - bail out (this should not happen either)
|
||||
assert False
|
||||
return False # Continue execution
|
||||
|
||||
# Remove the objfile and all debug info associated with it.
|
||||
objfile.unlink()
|
||||
|
||||
return False # Continue execution
|
||||
|
||||
# Create breakpoint to discard old code
|
||||
JITUnregisterCode("jit_unregister_code", internal=True)
|
||||
Reference in New Issue
Block a user