Wie soll ich die Ausgabe von dis.dis verstehen?

73

Ich würde gerne verstehen, wie man dis (den Dissembler von Python-Bytecode) benutzt . Wie sollte man konkret die Ausgabe von dis.dis(oder dis.disassemble) interpretieren ?

.

Hier ist ein sehr spezifisches Beispiel (in Python 2.7.3):

dis.dis("heapq.nsmallest(d,3)")

      0 BUILD_SET             24933
      3 JUMP_IF_TRUE_OR_POP   11889
      6 JUMP_FORWARD          28019 (to 28028)
      9 STORE_GLOBAL          27756 (27756)
     12 LOAD_NAME             29811 (29811)
     15 STORE_SLICE+0  
     16 LOAD_CONST            13100 (13100)
     19 STORE_SLICE+1

Ich sehe, dass JUMP_IF_TRUE_OR_POPusw. Bytecode-Anweisungen sind (obwohl interessanterweise BUILD_SETnicht in dieser Liste aufgeführt, obwohl ich davon ausgehe, dass es so funktioniert BUILD_TUPLE) . Ich denke, die Zahlen auf der rechten Seite sind Speicherzuordnungen, und die Zahlen auf der linken Seite sind Goto- Zahlen ... Ich stelle fest, dass sie sich jedes Mal fast um 3 erhöhen (aber nicht ganz).

Wenn ich mich dis.dis("heapq.nsmallest(d,3)")in eine Funktion einwickle :

def f_heapq_nsmallest(d,n):
    return heapq.nsmallest(d,n)

dis.dis("f_heapq(d,3)")

      0 BUILD_TUPLE            26719
      3 LOAD_NAME              28769 (28769)
      6 JUMP_ABSOLUTE          25640
      9 <44>                                      # what is <44> ?  
     10 DELETE_SLICE+1 
     11 STORE_SLICE+1 
Andy Hayden
quelle
1
Siehe auch: stackoverflow.com/questions/3299648/…
Andy Hayden

Antworten:

94

Sie versuchen, eine Zeichenfolge mit Quellcode zu zerlegen, dies wird jedoch dis.disin Python 2 nicht unterstützt . Mit einem Zeichenfolgenargument wird die Zeichenfolge so behandelt, als ob sie Bytecode enthält (siehe Funktion disassemble_stringindis.py ). Sie sehen also eine unsinnige Ausgabe, die auf einer Fehlinterpretation des Quellcodes als Bytecode basiert.

In Python 3 sieht es anders aus, wo dis.disein String-Argument kompiliert wird, bevor es zerlegt wird:

Python 3.2.3 (default, Aug 13 2012, 22:28:10) 
>>> import dis
>>> dis.dis('heapq.nlargest(d,3)')
  1           0 LOAD_NAME                0 (heapq) 
              3 LOAD_ATTR                1 (nlargest) 
              6 LOAD_NAME                2 (d) 
              9 LOAD_CONST               0 (3) 
             12 CALL_FUNCTION            2 
             15 RETURN_VALUE         

In Python 2 müssen Sie den Code selbst kompilieren, bevor Sie ihn an dis.disfolgende Adresse übergeben :

Python 2.7.3 (default, Aug 13 2012, 18:25:43) 
>>> import dis
>>> dis.dis(compile('heapq.nlargest(d,3)', '<none>', 'eval'))
  1           0 LOAD_NAME                0 (heapq)
              3 LOAD_ATTR                1 (nlargest)
              6 LOAD_NAME                2 (d)
              9 LOAD_CONST               0 (3)
             12 CALL_FUNCTION            2
             15 RETURN_VALUE        

Was bedeuten die Zahlen? Die Nummer 1ganz links ist die Zeilennummer im Quellcode, aus dem dieser Bytecode kompiliert wurde. Die Zahlen in der linken Spalte sind der Versatz der Anweisung innerhalb des Bytecodes, und die Zahlen rechts sind die Opargs . Schauen wir uns den tatsächlichen Bytecode an:

>>> co = compile('heapq.nlargest(d,3)', '<none>', 'eval')
>>> co.co_code.encode('hex')
'6500006a010065020064000083020053'

Bei Offset 0 im Bytecode finden wir 65den Opcode für LOAD_NAMEmit dem oparg 0000; dann (bei Offset 3) 6aist der Opcode LOAD_ATTRmit 0100dem Oparg und so weiter. Beachten Sie, dass die Opargs in Little-Endian-Reihenfolge sind, also 0100die Nummer 1. Das undokumentierte opcodeModul enthält Tabellen opname, in denen Sie den Namen für jeden Opcode und opmapden Opcode für jeden Namen angeben:

>>> opcode.opname[0x65]
'LOAD_NAME'

Die Bedeutung des Opargs hängt vom Opcode ab. Für die gesamte Story müssen Sie die Implementierung der virtuellen CPython-Maschine inceval.c lesen . Für LOAD_NAMEund LOAD_ATTRdas oparg ist ein Index in der co_namesEigenschaft des Codeobjekts :

>>> co.co_names
('heapq', 'nlargest', 'd')

Denn LOAD_CONSTes ist ein Index in die co_constsEigenschaft des Codeobjekts:

>>> co.co_consts
(3,)

Denn CALL_FUNCTIONes ist die Anzahl der Argumente, die an die Funktion übergeben werden sollen, codiert in 16 Bit mit der Anzahl der normalen Argumente im niedrigen Byte und der Anzahl der Schlüsselwortargumente im hohen Byte.

Gareth Rees
quelle
2
Erstaunlich, gibt es eine Referenz / ein Tutorial / ein Buch, das all diese Details auf niedriger Ebene enthält? Ich möchte mehr graben
DevC
3
@ DevC: Codeobjekte werden mit dem inspectModul dokumentiert . Bytecode-Anweisungen werden mit dem disModul dokumentiert . Für die Implementierungsdetails der virtuellen CPython-Maschine müssen Sie den Quellcode einlesen ceval.c.
Gareth Rees
Also ist diese Show Little-Endian und zwei Bytes lang oparg?
Schemas
Ja; auch die NEXTARGund PEEKARGMakros inceval.c .
Gareth Rees
@GarethRees für eine Dokumentation über ceval.cSie können das Ausführungsmodell docs.python.org/3.3/reference/executionmodel.html
KeatsKelleher
58

Ich reposte meine Antwort auf eine andere Frage , um sicherzugehen, dass ich sie beim Googeln finde dis.dis().


Um die Antwort des großen Gareth Rees zu vervollständigen , finden Sie hier nur eine kleine spaltenweise Zusammenfassung, um die Ausgabe des zerlegten Bytecodes zu erläutern.

Zum Beispiel mit dieser Funktion:

def f(num):
    if num == 42:
        return True
    return False

Dies kann in (Python 3.6) zerlegt werden:

(1)|(2)|(3)|(4)|          (5)         |(6)|  (7)
---|---|---|---|----------------------|---|-------
  2|   |   |  0|LOAD_FAST             |  0|(num)
   |-->|   |  2|LOAD_CONST            |  1|(42)
   |   |   |  4|COMPARE_OP            |  2|(==)
   |   |   |  6|POP_JUMP_IF_FALSE     | 12|
   |   |   |   |                      |   |
  3|   |   |  8|LOAD_CONST            |  2|(True)
   |   |   | 10|RETURN_VALUE          |   |
   |   |   |   |                      |   |
  4|   |>> | 12|LOAD_CONST            |  3|(False)
   |   |   | 14|RETURN_VALUE          |   |

Jede Spalte hat einen bestimmten Zweck:

  1. Die entsprechende Zeilennummer im Quellcode
  2. Gibt optional den aktuell ausgeführten Befehl an (wenn der Bytecode beispielsweise von einem Frame-Objekt stammt )
  3. Ein Etikett, das eine mögliche JUMPvon einer früheren Anweisung zu dieser bezeichnet
  4. Die Adresse im Bytecode, die dem Byte-Index entspricht (dies sind Vielfache von 2, da Python 3.6 für jeden Befehl 2 Bytes verwendet, während dies in früheren Versionen variieren kann).
  5. Der Befehlsname (auch genannt opname ), die jeweils kurz erläutert in dem disModul und deren Umsetzung finden sich in ceval.c(der Kernschleife CPython)
  6. Das Argument (falls vorhanden) der Anweisung, das von Python intern verwendet wird, um einige Konstanten oder Variablen abzurufen, den Stapel zu verwalten, zu einer bestimmten Anweisung zu springen usw.
  7. Die menschenfreundliche Interpretation des Anweisungsarguments
Delgan
quelle
4
das ist, was ich suche! Aber ich finde nicht in offiziellen doc, thx
Tarjintor