Casi todos los desarrolladores de Java saben que los programas escritos en lenguaje Java se compilan inicialmente en código de bytes JVM y se almacenan como archivos de clase de formato estándar . Después de obtener dichos archivos de clase dentro de la máquina virtual y hasta que el compilador aún no los haya alcanzado, la JVM interpreta el código de bytes contenido en estos archivos de clase. Este artículo proporciona una descripción general de cómo funciona el intérprete con respecto al OpenJDK JVM HotSpot.
El contenido del artículo:
- El medio ambiente
- Ejecutar aplicación java
- Inicialización del intérprete y transferencia de control a código Java
- Ejemplo
El medio ambiente
Para experimentos, utilizamos el ensamblaje de la última revisión de OpenJDK JDK12 disponible con configuración de configuración automática
--enable-debug --with-native-debug-symbols=internal
en Ubuntu 18.04 / gcc 7.4.0.
--with-native-debug-symbols=internal
significa que, al construir el JDK, los símbolos debazh estarán contenidos en los propios binarios.
--enable-debug
: que el binario contendrá código de depuración adicional.
Construir JDK 12 en dicho entorno no es un proceso complicado. Todo lo que tenía que hacer era instalar JDK11 ( para compilar JDK n, se requiere JDK n-1 ) y entregar manualmente las bibliotecas necesarias para la configuración automática indicadas. Luego, ejecuta el comando
bash configure --enable-debug --with-native-debug-symbols=internal && make CONF=fastdebug images
y después de esperar un poco (en mi computadora portátil unos 10 minutos), obtenemos fastdebug build JDK 12.
En principio, sería suficiente instalar simplemente jdk desde los repositorios públicos y, además, entregar el paquete openjdk-xx-dbg con símbolos de depuración, donde xx es la versión jdk, pero el ensamblaje fastdebug proporciona funciones de depuración de gdb que pueden facilitar la vida en algunos casos. Por el momento, estoy usando activamente ps () , una función para ver los rastros de la pila Java desde gdb, y pfl () , una función para analizar la pila de marcos (muy útil al depurar el intérprete en gdb).
Ejemplo ps () y pfl ()Por ejemplo, considere el siguiente script gdb
# java file /home/dmitrii/jdk12/build/linux-x86_64-server-fastdebug/images/jdk/bin/java # SEGV-, HotSpot # SEGV . #, https://hg.openjdk.java.net/jdk/jdk12/file/06222165c35f/src/hotspot/cpu/x86/vm_version_x86.cpp#l361 handle SIGSEGV nostop noprint set breakpoint pending on set pagination off # , # # java- public static void main(String args[]) b PostJVMInit thread 2 commands # , # set $buf = (char *) malloc(1000) # #( ) b *AbstractInterpreter::_entry_table[0] thread 2 commands # rbx. # Method* set $mthd = ((Method *) $rbx) # $buf call $mthd->name_and_sig_as_C_string($buf, 1000) # , public static void main(String args) if strcmp()("Main.main([Ljava/lang/String;)V", $buf) == 0 # , # ps/pfl #( ps/pfl) b InterpreterRuntime::build_method_counters(JavaThread*, Method*) commands # , # delete breakpoints call ps() call pfl() c end end c end c end r -cp /home/dmitrii/jdk12/ Main
El resultado de ejecutar dicho script es:
"Executing ps" for thread: "main" #1 prio=5 os_prio=0 cpu=468,61ms elapsed=58,65s tid=0x00007ffff001b800 nid=0x5bfa runnable [0x00007ffff7fd9000] java.lang.Thread.State: RUNNABLE Thread: 0x00007ffff001b800 [0x5bfa] State: _running _has_called_back 0 _at_poll_safepoint 0 JavaThread state: _thread_in_Java 1 - frame( sp=0x00007ffff7fd9920, unextended_sp=0x00007ffff7fd9920, fp=0x00007ffff7fd9968, pc=0x00007fffd828748b) Main.main(Main.java:10) "Executing pfl" for thread: "main" #1 prio=5 os_prio=0 cpu=468,83ms elapsed=58,71s tid=0x00007ffff001b800 nid=0x5bfa runnable [0x00007ffff7fd9000] java.lang.Thread.State: RUNNABLE Thread: 0x00007ffff001b800 [0x5bfa] State: _running _has_called_back 0 _at_poll_safepoint 0 JavaThread state: _thread_in_Java [Describe stack layout] 0x00007ffff7fd99e0: 0x00007ffff7fd9b00 #2 entry frame call_stub word fp - 0 0x00007ffff7fd99d8: 0x00007ffff7fd9c10 call_stub word fp - 1 0x00007ffff7fd99d0: 0x00007fffd8287160 call_stub word fp - 2 0x00007ffff7fd99c8: 0x00007fffbf1fb3e0 call_stub word fp - 3 0x00007ffff7fd99c0: 0x000000000000000a call_stub word fp - 4 0x00007ffff7fd99b8: 0x00007ffff7fd9ce8 call_stub word fp - 5 0x00007ffff7fd99b0: 0x00007ffff7fd9a80 call_stub word fp - 6 0x00007ffff7fd99a8: 0x00007ffff001b800 call_stub word fp - 7 0x00007ffff7fd99a0: 0x00007ffff7fd9b40 call_stub word fp - 8 0x00007ffff7fd9998: 0x00007ffff7fd9c00 call_stub word fp - 9 0x00007ffff7fd9990: 0x00007ffff7fd9a80 call_stub word fp - 10 0x00007ffff7fd9988: 0x00007ffff7fd9ce0 call_stub word fp - 11 0x00007ffff7fd9980: 0x00007fff00001fa0 call_stub word fp - 12 0x00007ffff7fd9978: 0x0000000716a122b8 sp for #2 locals for #1 unextended_sp for #2 local 0 0x00007ffff7fd9970: 0x00007fffd82719f3 0x00007ffff7fd9968: 0x00007ffff7fd99e0 #1 method Main.main([Ljava/lang/String;)V @ 0 - 1 locals 1 max stack 0x00007ffff7fd9960: 0x00007ffff7fd9978 interpreter_frame_sender_sp 0x00007ffff7fd9958: 0x0000000000000000 interpreter_frame_last_sp 0x00007ffff7fd9950: 0x00007fffbf1fb3e0 interpreter_frame_method 0x00007ffff7fd9948: 0x0000000716a11c40 interpreter_frame_mirror 0x00007ffff7fd9940: 0x0000000000000000 interpreter_frame_mdp 0x00007ffff7fd9938: 0x00007fffbf1fb5e8 interpreter_frame_cache 0x00007ffff7fd9930: 0x00007ffff7fd9978 interpreter_frame_locals 0x00007ffff7fd9928: 0x00007fffbf1fb3d0 interpreter_frame_bcp 0x00007ffff7fd9920: 0x00007ffff7fd9920 sp for #1 interpreter_frame_initial_sp unextended_sp for #1
Como puede ver, en el caso de ps()
solo obtenemos la pila de llamadas, en el caso de pfl()
, la organización de la pila completa.
Ejecutar aplicación java
Antes de proceder a la discusión del intérprete directamente, revisaremos brevemente las acciones que se realizan antes de transferir el control al código de Java. Por ejemplo, tome un programa Java que "no hace nada en absoluto":
public class Main { public static void main(String args[]){ } }
e intente averiguar qué sucede cuando ejecuta una aplicación de este tipo:
javac Main.java && java Main
Lo primero que debe hacer para responder esta pregunta es encontrar y mirar el binario de Java, el que usamos para ejecutar todas nuestras aplicaciones JVM. En mi caso, se encuentra a lo largo del camino.
/home/dmitrii/jdk12/build/linux-x86_64-server-fastdebug/images/jdk/bin/java
.
Pero al final, no hay nada especial que ver. Este es un binario que, junto con los símbolos debazhnymi, toma solo 20 KB y se compila a partir de un solo lanzador de archivos de origen / main.c.
Todo lo que hace es recibir argumentos de la línea de comandos (char * argv []), leer los argumentos de la variable de entorno JDK_JAVA_OPTIONS , realizar un preprocesamiento y validación básicos (por ejemplo, no puede agregar una opción de terminal o un nombre de clase Principal a esta variable de entorno) y llamar a la función JLI_Launch con la lista de argumentos resultante.
La definición de la función JLI_Launch no está contenida en el binario de Java y, si observa sus dependencias directas:
$ ldd java linux-vdso.so.1 (0x00007ffcc97ec000) libjli.so => /home/dmitrii/jdk12/build/linux-x86_64-server-fastdebug/images/jdk/bin/./../lib/libjli.so (0x00007ff27518d000) // <--------- libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff274d9c000) libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007ff274b7f000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007ff27497b000) libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007ff27475c000) /lib64/ld-linux-x86-64.so.2 (0x00007ff27559f000)
puedes ver libjli.so que está vinculado a él. Esta biblioteca contiene una interfaz de iniciador, un conjunto de funciones que utiliza Java para inicializar e iniciar una máquina virtual, entre las cuales se encuentra JLI_Launch.
Lista completa de características de la interfaz. $ objdump -T -j .text libjli.so libjli.so: file format elf64-x86-64 DYNAMIC SYMBOL TABLE: 0000000000009280 g DF .text 0000000000000038 Base JLI_List_add 0000000000003330 g DF .text 00000000000001c3 Base JLI_PreprocessArg 0000000000008180 g DF .text 0000000000000008 Base JLI_GetStdArgs 0000000000008190 g DF .text 0000000000000008 Base JLI_GetStdArgc 0000000000007e50 g DF .text 00000000000000b8 Base JLI_ReportErrorMessage 000000000000a400 g DF .text 00000000000000df Base JLI_ManifestIterate 0000000000002e70 g DF .text 0000000000000049 Base JLI_InitArgProcessing 0000000000008000 g DF .text 0000000000000011 Base JLI_ReportExceptionDescription 0000000000003500 g DF .text 0000000000000074 Base JLI_AddArgsFromEnvVar 0000000000007f10 g DF .text 00000000000000e9 Base JLI_ReportErrorMessageSys 0000000000005840 g DF .text 00000000000000b8 Base JLI_ReportMessage 0000000000009140 g DF .text 000000000000003a Base JLI_SetTraceLauncher 0000000000009020 g DF .text 000000000000000a Base JLI_MemFree 0000000000008f90 g DF .text 0000000000000026 Base JLI_MemAlloc 00000000000059c0 g DF .text 0000000000002013 Base JLI_Launch 00000000000091c0 g DF .text 000000000000003b Base JLI_List_new 0000000000008ff0 g DF .text 0000000000000026 Base JLI_StringDup 0000000000002ec0 g DF .text 000000000000000c Base JLI_GetAppArgIndex
Después de la transferencia de control a JLI_Launch, se requieren varias acciones para iniciar la JVM, tales como:
Yo Cargar caracteres JVM HotSpot en la memoria y obtener un puntero a una función para crear una VM.
Todo el código JVM HotSpot se encuentra en la biblioteca libjvm.so. Después de determinar la ruta absoluta a libjvm.so, la biblioteca se carga en la memoria y el puntero a la función JNI_CreateJavaVM se arranca de ella . Este puntero de función se almacena y posteriormente se utiliza para crear e inicializar la máquina virtual.
Obviamente libjvm.so no está vinculado a libjli.so
II Análisis de argumentos pasados después del preprocesamiento.
Una función con el nombre parlante ParseArguments analiza los argumentos pasados desde la línea de comandos. Este analizador de argumentos define el modo de inicio de la aplicación
enum LaunchMode {
También convierte parte de los argumentos al formato -DpropertyName=propertyValue
, por ejemplo, -cp=/path
convierte a -Djava.class.path=/path
. Además, dicha SystemProperty
se almacena en la matriz global en el HotSpot JVM y se reenvía a java.lang.System::props
en la primera fase de inicialización (en JDK12, el mecanismo de inicialización de java.lang.System.props se ha modificado, más en esta confirmación ).
Los argumentos de análisis también descartan algunas opciones que no son procesadas por la JVM (por ejemplo --list-modules
, el procesamiento de esta opción ocurre directamente en el iniciador en este punto ).
III . Bifurca un hilo primordial y crea una VM en él
Pero si algo salió mal, se intenta iniciar la JVM en el hilo principal "solo inténtalo".
Después de estudiar la pregunta, encontré una de las posibles razones por las cuales la JVM no comienza en el hilo principal. El hecho es que (al menos en Linux) los subprocesos y el subproceso principal funcionan de manera diferente con la pila. El tamaño de main-thread'a está limitado por ulimit -s
, es decir Al establecer un valor arbitrariamente grande, obtenemos una pila arbitrariamente grande. El hilo principal usa algo similar a MAP_GROWSDOWN , pero no MAP_GROWSDOWN
. Usar MAP_GROWSDOWN
en su forma pura no es seguro y, si la memoria me sirve, está bloqueado. En mi máquina, MAP_GROWSDOWN
no agrega ningún efecto. La diferencia entre la asignación de subprocesos principal y MAP_GROWSDOWN es que ningún otro mmap
, con la excepción de MAP_FIXED
, podrá colisionar con el área de posible expansión de la pila. Todo lo que se necesita del software es establecer el valor rsp
correspondiente y luego el sistema operativo lo resolverá: y se procesará la falla de página y se establecerá la protección . Esta diferencia afecta a varios rastrillos: al determinar el tamaño de la pila de la secuencia actual , al crear páginas de protección
Por lo tanto, asumiremos que en este momento hemos analizado con éxito las opciones y creado un hilo para la VM. Después de eso, el subproceso bifurcado comienza a crear una máquina virtual y entra en la función Threads :: create_vm
En esta función, se hacen un número bastante grande magia negra inicializaciones, solo nos interesarán algunas de ellas.
Inicialización del intérprete y transferencia de control al código java.
Para cada instrucción en el JVM HotSpot hay una plantilla de código de máquina específica para una arquitectura específica. Cuando el intérprete comienza a ejecutar una instrucción, lo primero que busca es la dirección de su plantilla en la tabla especial DispatchTable . Luego, salte a la dirección de esta plantilla y después de que se complete la ejecución de la instrucción, jvm saca la dirección de la siguiente instrucción en orden ) y comienza a ejecutarla de la misma manera, y así sucesivamente. Este comportamiento se observa con el intérprete solo para instrucciones que no "despachan", por ejemplo, instrucciones aritméticas ( xsub
, xdiv
, etc., donde x
- i
, l
, f
, d
). Todo lo que hacen es realizar operaciones aritméticas.
En el caso de instrucciones de invocación de procedimientos ( invokestatic
, invokevirtual
, etc.), la siguiente instrucción a ejecutar será la primera instrucción en el procedimiento llamado. Dichas instrucciones por sí mismas anotan la dirección de la siguiente instrucción de bytecode que se ejecutará en su plantilla.
Para garantizar el funcionamiento de esta máquina en Threads::create_vm
, se realizan varias inicializaciones de las que depende el intérprete:
Yo Inicializar una tabla de códigos de bytes disponibles
Antes de continuar con la inicialización del intérprete, es necesario inicializar la tabla de códigos de byte utilizados. Se ejecuta en la función Bytecodes :: initialize y se presenta como una etiqueta muy legible. Su fragmento es el siguiente:
De acuerdo con esta tabla, para cada bytecode se establece su longitud (el tamaño siempre es de 1 byte, pero también puede haber un índice en ConstantPool
, así como códigos de byte anchos), nombre, bytecode y banderas:
bool Bytecodes::_is_initialized = false; const char* Bytecodes::_name [Bytecodes::number_of_codes]; BasicType Bytecodes::_result_type [Bytecodes::number_of_codes]; s_char Bytecodes::_depth [Bytecodes::number_of_codes]; u_char Bytecodes::_lengths [Bytecodes::number_of_codes]; Bytecodes::Code Bytecodes::_java_code [Bytecodes::number_of_codes]; unsigned short Bytecodes::_flags [(1<<BitsPerByte)*2];
Estos parámetros son más necesarios para generar el código de la plantilla de intérprete.
II Inicializar código de caché
Para generar código para plantillas de intérprete, primero debe asignar memoria para este negocio. La reserva de memoria para el código de caché se implementa en una función del mismo nombre CodeCache :: initialize () . Como se puede ver en la siguiente sección de código de esta función
CodeCacheExpansionSize = align_up(CodeCacheExpansionSize, os::vm_page_size()); if (SegmentedCodeCache) {
el código de caché está controlado por las opciones -XX:ReservedCodeCacheSize
, -XX:SegmentedCodeCache
, -XX:CodeCacheExpansionSize
, -XX:NonNMethodCodeHeapSize
, -XX:ProfiledCodeHeapSize
, -XX:NonProfiledCodeHeapSize
. Se puede encontrar una breve descripción de estas opciones en los enlaces a los que conducen. Además de la línea de comando, los valores de algunas de estas opciones se ajustan ergonómicamente, por ejemplo, si el valor SegmentedCodeCache
se SegmentedCodeCache
por defecto (desactivado), entonces con un tamaño de código >= 240Mb
, >= 240Mb
se incluirá en CompilerConfig :: set_tiered_flags .
Después de realizar comprobaciones, se ReservedCodeCacheSize
un área de tamaño ReservedCodeCacheSize
bytes. Si SegmentedCodeCache
resultó estar expuesto, entonces esta área se divide en partes: métodos compilados JIT, rutinas de puñaladas, etc.
III . Inicialización de patrones de interpretación.
Después de inicializar la tabla de bytecode y el código de caché, puede continuar con la generación del código de las plantillas de intérprete. Para hacer esto, el intérprete reserva un búfer del código de caché inicializado previamente. En cada etapa de generación de código, los codelets , pequeñas secciones de código, se cortarán del búfer . Después de completar la generación actual, la parte del codelet que no utiliza el código se libera y queda disponible para las generaciones de código posteriores.
Considere cada uno de estos pasos individualmente:
{ CodeletMark cm(_masm, "slow signature handler"); AbstractInterpreter::_slow_signature_handler = generate_slow_signature_handler(); }
El controlador de firma se utiliza para preparar argumentos para llamadas a métodos nativos. En este caso, se genera un controlador genérico si, por ejemplo, el método nativo tiene más de 13 argumentos (no lo verifiqué en el depurador, pero a juzgar por el código debería ser así)
{ CodeletMark cm(_masm, "error exits"); _unimplemented_bytecode = generate_error_exit("unimplemented bytecode"); _illegal_bytecode_sequence = generate_error_exit("illegal bytecode sequence - method not verified"); }
La VM valida los archivos de clase durante la inicialización, pero esto es en caso de que los argumentos en la pila no estén en el formato que se necesita o el código de bytes que la VM no conoce. Estos apéndices se utilizan al generar código de plantilla para cada uno de los códigos de bytes.
Después de llamar a los procedimientos, es necesario restaurar los datos de la pila de cuadros, que era antes de la llamada al procedimiento desde el que se realiza la devolución.
Se usa cuando se llama a tiempo de ejecución desde un intérprete.
Lanzando excepciones
Método Puntos de entrada
#define method_entry(kind) \ { CodeletMark cm(_masm, "method entry point (kind = " #kind ")"); \ Interpreter::_entry_table[Interpreter::kind] = generate_method_entry(Interpreter::kind); \ Interpreter::update_cds_entry_table(Interpreter::kind); \ }
Presentado como una macro según el tipo de método. En el caso general, se realiza la preparación del marco de pila interpretado, comprobación de StackOverflow, apilamiento de pila. Para los métodos nativos, se define un controlador de firma.
Generación de plantillas de código de bytes
Para ejecutar la instrucción, la especificación de VM requiere que los operandos estén en la pila de operandos , pero esto no impide que HotSpot los almacene en caché en el registro. Para determinar el estado actual de la parte superior de la pila, se utiliza una enumeración.
enum TosState {
Cada instrucción define los estados de entrada y salida de la TosState
superior de la pila TosState
, y la generación de patrones ocurre dependiendo de este estado. Estas plantillas se inicializan en una tabla de plantillas legibles. Un fragmento de esta tabla es el siguiente:
Estaremos especialmente interesados in
columnas de out
y generator
.
in
: el estado de la parte superior de la pila en el momento en que comenzó la instrucción
out
- estado de la parte superior de la pila al momento de completar la instrucción
generator
- generador de plantilla de código de instrucción de máquina
La vista general de la plantilla para todos los códigos de bytes se puede describir como:
Si el bit de despacho no está configurado para la instrucción, se ejecuta el prólogo de la instrucción (no operativo en x86)
Usando el generator
, se genera el código de máquina
Si el bit de despacho no está establecido para la instrucción, la transición a la siguiente instrucción en orden se realiza en función del estado de out
de la parte superior de la pila, que estará in
la próxima instrucción
La dirección del punto de entrada para la plantilla resultante se almacena en la tabla global y se puede usar para la depuración.
En HotSpot, el siguiente fragmento de código relativamente tonto es responsable de esto:
Generador de código de instrucciones void TemplateInterpreterGenerator::set_entry_points(Bytecodes::Code code) { CodeletMark cm(_masm, Bytecodes::name(code), code);
, . JVM. Java- . JavaCalls . JVM , main .
Ejemplo
, , :
public class Sum{ public static void sum(int a, int b){ return a + b; } } public class Main { public static void main(String args[]){ Sum.sum(2, 3); } }
Sum.sum(II)
.
2 javac -c *.java
, .
Sum.sum
:
descriptor: (II)I flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=2 0: iload_0 1: iload_1 2: iadd 3: ireturn LineNumberTable: line 3: 0
Main.main
descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: iconst_2 1: iconst_3 2: invokestatic #2 // Method Sum.sum:(II)I 5: pop 6: return LineNumberTable: line 13: 0 line 14: 6
, — .
invokestatic
' x86 - HotSpot
void TemplateTable::invokestatic(int byte_no) { transition(vtos, vtos); assert(byte_no == f1_byte, "use this argument"); prepare_invoke(byte_no, rbx);
byte_no == f1_byte
— ConstantPoolCache
, , rbx
— , Method *
. : , , ( method_entry
).
prepare_invoke
. , invokestatic
ConstantPool
Constant_Methodref_Info
. HotSpot . 2 .. ConstantPoolCache
. ConstantPoolCache
, (, ConstantPoolCacheEntry
, ). ConstantPoolCacheEntry
, ( 0) / . , ConstantPool
, ConstantPoolCache
( x86 Little Endian).
, , HotSpot prepare_invoke
— ConstantPoolCache
. , , ConstantPoolCacheEntry
__ get_cache_and_index_and_bytecode_at_bcp(Rcache, index, temp, byte_no, 1, index_size); __ cmpl(temp, code);
, InterpreterRuntime::resolve_from_cache
.
receiver'a , . (, , , ConstantPoolCache
<clinit>
, ). define class, EagerInitialization
( , , :)). HotSpot ( CDS ) .
, , ConstantPoolCacheEntry
. Method *
rbx
, , .
Sum.sum(2, 3)
. gdb-script sum.gdb
:
# java file /home/dmitrii/jdk12/build/linux-x86_64-server-fastdebug/images/jdk/bin/java # gdb SEGV' #, https://hg.openjdk.java.net/jdk/jdk12/file/06222165c35f/src/hotspot/cpu/x86/vm_version_x86.cpp#l361 handle SIGSEGV nostop noprint # set breakpoint pending on # , # set pagination off # main b PostJVMInit commands # , # set $buffer = malloc(1000) # . #jmp # invokestatic b *AbstractInterpreter::_entry_table[0] thread 2 commands # invokestatic, # Method* rbx set $mthd = (Method *) $rbx # $buffer call $mthd->name_and_sig_as_C_string($buffer, 1000) if strcmp()($buffer, "Sum.sum(II)I") == 0 # iload_0, b *TemplateInterpreter::_normal_table._table[vtos][26] thread 2 # iload_1, - int, # iload_0 b *TemplateInterpreter::_normal_table._table[itos][27] thread 2 # iadd b *TemplateInterpreter::_normal_table._table[itos][96] thread 2 end c end c end r -cp . Main
gdb -x sum.gdb
, Sum.sum
$453 = 0x7ffff7fdcdd0 "Sum.sum(II)I"
layout asm
, , generate_normal_entry . -, StackOverflow, stack-banging dispatch iload_0
. :
0x7fffd828fa1f mov eax,DWORD PTR [r14] ;, iload_0 0x7fffd828fa22 movzx ebx,BYTE PTR [r13+0x1] ; 0x7fffd828fa27 inc r13 ; bcp (byte code pointer) 0x7fffd828fa2a movabs r10,0x7ffff717e8a0 ; DispatchTable 0x7fffd828fa34 jmp QWORD PTR [r10+rbx*8] ;jump
rax
,
0x7fffd828fabe push rax ; ; , 0x7fffd828fabf mov eax,DWORD PTR [r14-0x8] 0x7fffd828fac3 movzx ebx,BYTE PTR [r13+0x1] 0x7fffd828fac8 inc r13 0x7fffd828facb movabs r10,0x7ffff717e8a0 0x7fffd828fad5 jmp QWORD PTR [r10+rbx*8]
iadd
:
0x7fffd8292ba7 mov edx,DWORD PTR [rsp] ; , iload_1 0x7fffd8292baa add rsp,0x8 ; rsp 0x7fffd8292bae add eax,edx ; 0x7fffd8292bb0 movzx ebx,BYTE PTR [r13+0x1] 0x7fffd8292bb5 inc r13 0x7fffd8292bb8 movabs r10,0x7ffff717e8a0 0x7fffd8292bc2 jmp QWORD PTR [r10+rbx*8]
gdb
eax
edx
,
(gdb) p $eax $457 = 3 (gdb) p $edx $458 = 2
, Sum.sum
.