Presque tous les développeurs Java savent que les programmes écrits en Java sont initialement compilés dans un bytecode JVM et stockés sous forme de fichiers de classe dans un format standardisé . Après avoir placé ces fichiers de classe dans la machine virtuelle et jusqu'à ce que le compilateur ne les ait pas encore atteints, la JVM interprète le bytecode contenu dans ces fichiers de classe. Cet article fournit un aperçu du fonctionnement de l'interpréteur par rapport au HotSpot OpenJDK JVM.
Le contenu de l'article:
- L'environnement
- Exécution d'une application Java
- Initialisation de l'interpréteur et transfert de contrôle en code java
- Exemple
L'environnement
Pour les expériences, nous utilisons l'assemblage de la dernière révision OpenJDK JDK12 disponible avec la configuration autoconf
--enable-debug --with-native-debug-symbols=internal
sur Ubuntu 18.04 / gcc 7.4.0.
--with-native-debug-symbols=internal
signifie que, lors de la construction du JDK, les symboles debazh seront contenus dans les binaires eux-mêmes.
--enable-debug
- que le binaire contiendra du code de débogage supplémentaire.
Construire JDK 12 dans un tel environnement n'est pas un processus compliqué. Tout ce que je devais faire était d'installer JDK11 ( pour construire JDK n, JDK n-1 est requis ) et livrer manuellement les bibliothèques nécessaires signalées par autoconf. Ensuite, exécutez la commande
bash configure --enable-debug --with-native-debug-symbols=internal && make CONF=fastdebug images
et après avoir attendu un peu (sur mon ordinateur portable environ 10 minutes), nous obtenons la construction JDK 12 de fastdebug.
En principe, il suffirait d'installer simplement jdk à partir de référentiels publics et de fournir en plus le paquet openjdk-xx-dbg avec des symboles de débogage, où xx est la version jdk, mais l'assemblage fastdebug fournit des fonctions de débogage à partir de gdb qui peuvent faciliter la vie dans certains cas. Pour le moment, j'utilise activement ps () , une fonction pour afficher les traces de pile Java à partir de gdb, et pfl () , une fonction pour analyser la pile de trames (c'est très pratique lors du débogage de l'interpréteur dans gdb).
Exemple ps () et pfl ()Par exemple, considérez le script gdb suivant
# 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
Le résultat de l'exécution d'un tel script est:
"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
Comme vous pouvez le voir, dans le cas de ps()
nous obtenons simplement la pile des appels, dans le cas de pfl()
- l'organisation complète de la pile.
Exécution d'une application Java
Avant de passer directement à la discussion de l'interpréteur, nous passerons brièvement en revue les actions qui sont effectuées avant de transférer le contrôle au code java. Par exemple, prenez un programme Java qui "ne fait rien du tout":
public class Main { public static void main(String args[]){ } }
et essayez de comprendre ce qui se passe lorsque vous exécutez une telle application:
javac Main.java && java Main
La première chose à faire pour répondre à cette question est de trouver et d'examiner le binaire java - celui que nous utilisons pour exécuter toutes nos applications JVM. Dans mon cas, il est situé le long du chemin
/home/dmitrii/jdk12/build/linux-x86_64-server-fastdebug/images/jdk/bin/java
.
Mais au final, il n'y a rien de spécial à regarder. Il s'agit d'un binaire qui, avec les symboles debazhnymi, ne prend que 20 Ko et est compilé à partir d'un seul lanceur de fichier source / main.c.
Il ne fait que recevoir des arguments de ligne de commande (char * argv []), lire les arguments de la variable d'environnement JDK_JAVA_OPTIONS , effectuer un prétraitement et une validation de base (par exemple, vous ne pouvez pas ajouter d' option de terminal ou de nom de classe principale à cette variable d'environnement) et appeler la fonction JLI_Launch avec la liste d'arguments résultante.
La définition de la fonction JLI_Launch n'est pas contenue dans le binaire java et, si vous regardez ses dépendances directes:
$ 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)
vous pouvez voir libjli.so qui lui est lié. Cette bibliothèque contient une interface de lancement - un ensemble de fonctions que java utilise pour initialiser et démarrer une machine virtuelle, parmi lesquelles il y a JLI_Launch.
Liste complète des fonctionnalités de l'interface $ 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
Après le transfert de contrôle à JLI_Launch, un certain nombre d'actions sont nécessaires pour démarrer la JVM, telles que:
I. Chargement de caractères JVM HotSpot dans la mémoire et obtention d'un pointeur sur une fonction pour créer une machine virtuelle.
Tout le code JVM HotSpot se trouve dans la bibliothèque libjvm.so. Après avoir déterminé le chemin absolu vers libjvm.so, la bibliothèque est chargée en mémoire et le pointeur vers la fonction JNI_CreateJavaVM en est arraché . Ce pointeur de fonction est stocké et ensuite utilisé pour créer et initialiser la machine virtuelle.
De toute évidence, libjvm.so n'est pas lié à libjli.so
II . Analyse des arguments passés après le prétraitement.
Une fonction avec le nom parlant ParseArguments analyse les arguments passés à partir de la ligne de commande. Cet analyseur d'arguments définit le mode de démarrage de l'application
enum LaunchMode {
Il convertit également une partie des arguments au format -DpropertyName=propertyValue
, par exemple, -cp=/path
converti en -Djava.class.path=/path
. De plus, ces SystemProperty
sont stockées dans le tableau global dans le JVM HotSpot et transmises à java.lang.System::props
dans la première phase d'initialisation (dans JDK12, le mécanisme d'initialisation de java.lang.System.props a été modifié, plus dans cette validation ).
L'analyse des arguments supprime également certaines options qui ne sont pas traitées par la machine --list-modules
(par exemple, --list-modules
, le traitement de cette option a lieu directement dans le lanceur à ce stade ).
III . Créez un thread primordial et créez-y une machine virtuelle
Mais si quelque chose s'est mal passé, une tentative est faite pour démarrer la JVM dans le thread principal "essayez juste".
Après avoir étudié la question, j'ai trouvé l'une des raisons possibles pour lesquelles la JVM ne démarre pas dans le thread principal. Le fait est que (au moins sous Linux) pthreads et le thread principal fonctionnent différemment avec la pile. La taille du thread principal'a est limitée par ulimit -s
, c'est-à-dire lors de la définition d'une valeur arbitrairement grande, nous obtenons une pile arbitrairement grande. Le thread principal utilise quelque chose de similaire à MAP_GROWSDOWN , mais pas MAP_GROWSDOWN
. L'utilisation de MAP_GROWSDOWN
dans sa forme pure n'est pas sûre et, si la mémoire est bonne, elle est verrouillée. Sur ma machine, MAP_GROWSDOWN
n'ajoute aucun effet. La différence entre le mappage de thread principal et MAP_GROWSDOWN est qu'aucun autre mmap
, à l'exception de MAP_FIXED
, ne pourra créer de conflit avec la zone d'expansion possible de la pile. Tout ce qui est nécessaire à partir du logiciel est de définir la valeur rsp
correspondante, puis le système d'exploitation le comprendra: Et le défaut de page sera traité et le protecteur se mettra en place . Cette différence affecte un certain nombre de râteaux: lors de la détermination de la taille de la pile du flux actuel , lors de la création de pages de garde
Ainsi, nous supposerons qu'au moment où nous avons analysé avec succès les options et créé un thread pour la machine virtuelle. Après cela, le thread juste bifurqué commence à créer une machine virtuelle et entre dans la fonction Threads :: create_vm
Dans cette fonction, un nombre assez important est fait magie noire initialisations, nous ne nous intéresserons qu'à quelques-unes d'entre elles.
Initialisation de l'interpréteur et transfert de contrôle au code java
Pour chaque instruction du JVM HotSpot, il existe un modèle de code machine spécifique pour une architecture spécifique. Lorsque l'interpréteur commence à exécuter une instruction, la première chose qu'il recherche est l'adresse de son modèle dans la table spéciale DispatchTable . Ensuite, passez à l'adresse de ce modèle et une fois l'exécution de l'instruction terminée, jvm supprime l'adresse de la prochaine instruction dans l'ordre ) et commence à l'exécuter de la même manière, etc. Ce comportement est observé avec l'interpréteur uniquement pour les instructions qui ne "distribuent" pas, par exemple, les instructions arithmétiques ( xsub
, xdiv
, etc., où x
- i
, l
, f
, d
). Tout ce qu'ils font, c'est effectuer des opérations arithmétiques.
Dans le cas d'instructions d'invocation de procédures ( invokestatic
, invokevirtual
, etc.), la prochaine instruction à exécuter sera la première instruction de la procédure appelée. De telles instructions elles-mêmes notent l'adresse de la prochaine instruction de bytecode à exécuter dans leur modèle.
Pour assurer le fonctionnement de cette machine dans Threads::create_vm
, un certain nombre d'initialisations sont effectuées dont dépend l'interpréteur:
I. Initialisation d'une table des bytecodes disponibles
Avant de procéder à l'initialisation de l'interpréteur, il est nécessaire d'initialiser la table des bytecodes utilisés. Il est exécuté dans la fonction Bytecodes :: initialize et est présenté comme une étiquette très lisible. Son fragment est le suivant:
Conformément à ce tableau, pour chaque bytecode, sa longueur est définie (la taille est toujours de 1 octet, mais il peut également y avoir un index dans ConstantPool
, ainsi que des bytecodes larges), nom, bytecode et drapeaux:
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];
Ces paramètres sont en outre nécessaires pour générer du code de modèle d'interpréteur.
II . Initialiser le code de cache
Afin de générer du code pour les modèles d'interpréteur, vous devez d'abord allouer de la mémoire à cette entreprise. La réservation de mémoire pour le code cache est implémentée dans une fonction du même nom CodeCache :: initialize () . Comme on peut le voir dans la section de code suivante de cette fonction
CodeCacheExpansionSize = align_up(CodeCacheExpansionSize, os::vm_page_size()); if (SegmentedCodeCache) {
le code de cache est contrôlé par les options -XX:ReservedCodeCacheSize
, -XX:SegmentedCodeCache
, -XX:CodeCacheExpansionSize
, -XX:NonNMethodCodeHeapSize
, -XX:ProfiledCodeHeapSize
, -XX:NonProfiledCodeHeapSize
. Une brève description de ces options se trouve sur les liens auxquels elles mènent. En plus de la ligne de commande, les valeurs de certaines de ces options sont ajustées de façon ergonomique, par exemple, si la valeur SegmentedCodeCache
est SegmentedCodeCache
par défaut (désactivée), puis avec une taille de code >= 240Mb
, SegmentedCodeCache
sera inclus dans CompilerConfig :: set_tiered_flags .
Après avoir effectué les vérifications, une zone de taille octets ReservedCodeCacheSize
est ReservedCodeCacheSize
. Si SegmentedCodeCache
s'est avéré être exposé, alors cette zone est divisée en parties: méthodes compilées JIT, routines de stabilisation, etc.
III . Initialisation des modèles d'interpréteur
Une fois la table de bytecode et le code de cache initialisés, vous pouvez procéder à la génération de code des modèles d'interpréteur. Pour ce faire, l'interpréteur réserve un tampon à partir du code de cache précédemment initialisé. À chaque étape de la génération de code, les codelets - de petites sections de code - seront coupés du tampon . Une fois la génération en cours terminée, la partie du codelet qui n'est pas utilisée par le code est libérée et devient disponible pour les générations de code suivantes.
Considérez chacune de ces étapes individuellement:
{ CodeletMark cm(_masm, "slow signature handler"); AbstractInterpreter::_slow_signature_handler = generate_slow_signature_handler(); }
le gestionnaire de signature est utilisé pour préparer des arguments pour les appels aux méthodes natives. Dans ce cas, un gestionnaire générique est généré si, par exemple, la méthode native a plus de 13 arguments (je ne l'ai pas vérifié dans le débogueur, mais à en juger par le code, cela devrait être comme ça)
{ 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 machine virtuelle valide les fichiers de classe lors de l'initialisation, mais c'est dans le cas où les arguments sur la pile ne sont pas au format requis ou au bytecode que la machine virtuelle ne connaît pas. Ces stubs sont utilisés lors de la génération de code de modèle pour chacun des codes secondaires.
Après avoir appelé les procédures, il est nécessaire de restaurer les données de la pile de trames, qui étaient avant l'appel de la procédure à partir de laquelle le retour est effectué.
Utilisé lors de l'appel du runtime à partir d'un interprète.
Lancer des exceptions
Points d'entrée de méthode
#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); \ }
Présenté sous forme de macro selon le type de méthode. Dans le cas général, la préparation de la trame de pile interprétée est effectuée, vérification StackOverflow, pile-banging. Pour les méthodes natives, un gestionnaire de signature est défini.
Génération de modèles de bytecode
Pour exécuter l'instruction, la spécification VM requiert que les opérandes soient dans la pile d'opérandes , mais cela n'empêche pas HotSpot de les mettre en cache dans le registre. Une énumération est utilisée pour déterminer l'état actuel du haut de la pile.
enum TosState {
Chaque instruction définit les états d'entrée et de sortie du sommet TosState
de la pile, et la génération de modèles se produit en fonction de cet état. Ces modèles sont initialisés dans une table de modèles lisibles. Un fragment de ce tableau est le suivant:
Nous serons particulièrement intéressés in
colonnes d' out
et de generator
.
in
- état du haut de la pile au moment où l'instruction a commencé
out
- état du haut de la pile au moment de l'achèvement
generator
- générateur de modèle de code d'instruction machine
La vue générale du modèle pour tous les bytecodes peut être décrite comme suit:
Si le bit de répartition n'est pas défini pour l'instruction, le prologue de l'instruction est exécuté (no-op sur x86)
À l'aide du generator
, le code machine est généré
Si le bit de répartition n'est pas défini pour l'instruction, la transition vers l'instruction suivante dans l'ordre est effectuée en fonction de l'état de out
du haut de la pile, qui sera entré pour l'instruction suivante
L'adresse du point d'entrée pour le modèle résultant est stockée dans la table globale et peut être utilisée pour le débogage.
Dans HotSpot, le code relativement stupide suivant en est responsable:
Générateur de codes d'instructions void TemplateInterpreterGenerator::set_entry_points(Bytecodes::Code code) { CodeletMark cm(_masm, Bytecodes::name(code), code);
, . JVM. Java- . JavaCalls . JVM , main .
Exemple
, , :
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
.