Frage Wie verfolgt eine Makro-fähige Sprache den Quellcode für das Debuggen?


Dies ist eine eher theoretische Frage zu Makros (glaube ich). Ich weiß, dass Makros Quellcode verwenden und Objektcode erzeugen, ohne ihn zu evaluieren, was es Programmierern ermöglicht, vielseitige syntaktische Strukturen zu erstellen. Wenn ich diese beiden Makrosysteme klassifizieren müsste, würde ich sagen, dass es das Makro "C style" und das Makro "Lisp style" gibt.

Es scheint, dass das Debugging von Makros ein bisschen schwierig sein kann, da sich zur Laufzeit der Code, der tatsächlich ausgeführt wird, von der Quelle unterscheidet.

Wie verfolgt der Debugger die Ausführung des Programms hinsichtlich des vorverarbeiteten Quellcodes? Gibt es einen speziellen "Debug-Modus", der gesetzt werden muss, um zusätzliche Daten über das Makro zu erfassen?

In C kann ich verstehen, dass Sie eine Kompilierzeit für das Debuggen festlegen würden, aber wie würde eine interpretierte Sprache, wie einige Formen von Lisp, dies tun?

Entschuldige dich dafür, dass du das nicht ausprobiert hast, aber die lisp-Toolchain benötigt mehr Zeit als ich ausgeben muss, um es herauszufinden.


9
2017-07-09 16:25


Ursprung


Antworten:


Ich glaube nicht, dass es einen fundamentalen Unterschied zwischen den Makros "C style" und "Lisp style" gibt, in denen sie kompiliert werden. Beide transformieren die Quelle, bevor der eigentliche Compiler sie sieht. Der große Unterschied besteht darin, dass die Makros von C den C-Präprozessor verwenden (eine schwächere Sekundärsprache, die hauptsächlich für die einfache String-Substitution verwendet wird), während die Lisp-Makros in Lisp selbst geschrieben sind (und somit überhaupt etwas tun können).

(Als Nebenbemerkung: Ich habe seit einiger Zeit kein nicht kompiliertes Lisp gesehen ... sicherlich nicht seit der Jahrhundertwende. Aber wenn überhaupt, dann scheint das Makro-Debugging-Problem einfacher zu sein, seither Sie haben mehr Informationen um.)

Ich stimme Michael zu: Ich habe keinen Debugger für C gesehen, der überhaupt mit Makros arbeitet. Code, der Makros verwendet, wird transformiert, bevor etwas passiert. Das "Debug" -Modus zum Kompilieren von C-Code im Allgemeinen bedeutet nur, dass es speichert Funktionen, Typen, Variablen, Dateinamen und so weiter - Ich glaube nicht, dass einer von ihnen Informationen über Makros speichert.

  • Für das Debuggen von Programmen das benutzen MakrosLisp ist ziemlich ähnlich wie C hier: Ihr Debugger sieht die kompilierter Code, nicht das Makro Anwendung. In der Regel sind Makros einfach gehalten und debuggt unabhängig vor dem Gebrauch, zu vermeiden die Notwendigkeit dafür, genau wie C.

  • Zum Debuggen der Makros sichBevor Sie loslegen und es irgendwo benutzen, hat Lisp Funktionen das macht das einfacher als in C, z.B. die repl und macroexpand-1 (obwohl in C Es gibt offensichtlich einen Weg dazu macroexpand eine ganze Datei, vollständig, um Einmal). Du kannst das ... sehen Vorher-Nachher einer Makroexpansion, direkt in deinem Editor, wenn du schreibst es.

Ich kann mich an keine Zeit mehr erinnern, in der ich eine Debugging-Situation erlebt habe in eine Makrodefinition selbst wäre nützlich gewesen. Entweder ist es ein Fehler in der Makrodefinition, in diesem Fall macroexpand-1 isoliert das Problem sofort, oder es ist ein Fehler darunter, in welchem ​​Fall die normalen Debugging-Einrichtungen gut funktionieren und es mir egal ist, dass eine Makroexpansion zwischen zwei Frames meines Aufruf-Stacks aufgetreten ist.


3
2017-07-09 18:02



Im LispWorks Entwickler können das verwenden Stepper-Werkzeug.

LispWorks bietet einen Stepper, bei dem man durch das Ganze treten kann Makroexpansionsprozess.


3
2017-07-09 21:26



Sie sollten wirklich in die Art der Unterstützung schauen, die Schläger hat zum Debuggen von Code mit Makros. Diese Unterstützung hat zwei Aspekte, wie Ken erwähnt. Auf der einen Seite gibt es das Problem der Fehlersuche Makros: in Common Lisp ist der beste Weg, um Makroformen nur manuell zu erweitern. Mit CPP ist die Situation ähnlich, aber primitiver - Sie führen den Code nur durch die CPP-Erweiterung und überprüfen das Ergebnis. Beide sind jedoch nicht ausreichend für Makros mit größerer Beteiligung, und dies war die Motivation für eine Makro-Debugger im Racket - zeigt Ihnen die einzelnen Schritte der Syntaxerweiterung mit zusätzlichen Gui-basierten Anzeigen für Dinge wie gebundene Bezeichner usw.

Auf der Seite von verwenden Makros war Racket immer fortschrittlicher als andere Scheme- und Lisp-Implementierungen. Die Idee ist, dass jeder Ausdruck (als ein syntaktisches Objekt) der Code plus zusätzliche Daten ist, die seinen Quellort enthalten. Wenn ein Formular ein Makro ist, hat der erweiterte Code, der Teile hat, die aus dem Makro stammen, den richtigen Quellort - von der Definition des Makros und nicht von seiner Verwendung (wo die Formulare nicht wirklich vorhanden sind). Einige Scheme- und Lisp-Implementierungen implementieren eine begrenzte dafür unter Verwendung der Identität von Unterformularen, wie dmitry-vk erwähnt.


2
2017-07-09 18:18



Ich weiß nichts über Lisp-Makros (von denen ich vermute, dass sie sich von C-Makros unterscheiden) oder Debugging, aber viele - wahrscheinlich die meisten - C / C ++ - Debugger beherrschen das Debuggen von C-Präprozessormakros auf Source-Ebene nicht besonders gut.

Im Allgemeinen werden C / C ++ - Debugger nicht in die Makrodefinition "versetzt". Wenn ein Makro in mehrere Anweisungen erweitert wird, bleibt der Debugger normalerweise immer in der gleichen Quellzeile (wo das Makro aufgerufen wird) für jeden Debuggerschritt.

Dies kann das Debuggen von Makros etwas schmerzhafter machen, als sie es sonst sein könnten - ein weiterer Grund, sie in C / C ++ zu vermeiden. Wenn sich ein Makro auf wirklich mysteriöse Weise falsch verhält, werde ich in den Assembly-Modus gehen, um es zu debuggen oder das Makro zu erweitern (entweder manuell oder mit dem Schalter des Compilers). Es ist ziemlich selten, dass man so extrem gehen muss; Wenn Sie Makros schreiben, die so kompliziert sind, gehen Sie wahrscheinlich den falschen Weg.


1
2017-07-09 17:26



Im C-Quellcode-Debugging wird normalerweise die Zeilengranularität ("next" -Befehl) oder die Granularität auf Anweisungsebene ("step in") verwendet. Makroprozessoren fügen spezielle Direktiven in die verarbeitete Quelle ein, die es dem Compiler ermöglichen, kompilierte Sequenzen von CPU-Anweisungen auf Quellcodezeilen abzubilden.

In Lisp gibt es keine Konvention zwischen Makros und Compiler, Quellcode zu kompiliertem Code-Mapping zu verfolgen, so dass es nicht immer möglich ist, Single-Stepping in Quellcode zu tun.

Offensichtliche Option ist, Einzelschritt in Makroexpanded-Code auszuführen. Der Compiler sieht bereits eine endgültige, erweiterte Version des Codes und kann Quellcode für das Maschinencode-Mapping verfolgen.

Eine andere Möglichkeit besteht darin, die Tatsache zu verwenden, dass Lisp-Ausdrücke während der Manipulation eine Identität haben. Wenn das Makro einfach ist und einfach Code in eine Schablone destrukturiert und einfügt, dann sind einige Ausdrücke des erweiterten Codes identisch (in Bezug auf den EQ-Vergleich) zu Ausdrücken, die aus dem Quellcode gelesen wurden. In diesem Fall kann der Compiler einige Ausdrücke vom erweiterten Code zum Quellcode zuordnen.


1
2017-07-09 18:18



Die einfache Antwort ist, dass es kompliziert ist ;-) Es gibt verschiedene Dinge, die dazu beitragen, ein Programm zu debuggen, und noch mehr, um Makros zu verfolgen.

In C und C ++ wird der Präprozessor zum Erweitern von Makros und zum Einbeziehen in tatsächlichen Quellcode verwendet. Die Originaldateinamen und Zeilennummern werden in dieser erweiterten Quelldatei mithilfe von #line-Direktiven verfolgt.

http://msdn.microsoft.com/en-us/library/b5w2czay(VS.80).aspx

Wenn ein C- oder C ++ - Programm mit aktiviertem Debugging kompiliert wird, generiert der Assembler zusätzliche Informationen in der Objektdatei, die Quellzeilen, Symbolnamen, Typdeskriptoren usw. verfolgen.

http://sources.redhat.com/gdb/onlinedocs/stabs.html

Das Betriebssystem verfügt über Funktionen, die es einem Debugger ermöglichen, eine Verbindung zu einem Prozess herzustellen und die Prozessausführung zu steuern. Pausieren, Einzelschritt usw.

Wenn ein Debugger an das Programm angehängt ist, übersetzt er den Prozessstapel und den Programmzähler zurück in symbolische Form, indem er die Bedeutung von Programmadressen in der Debugging-Information nachschlägt.

Dynamische Sprachen werden normalerweise in einer virtuellen Maschine ausgeführt, unabhängig davon, ob es sich um einen Interpreter oder eine Bytecode-VM handelt. Es ist die VM, die Hooks zur Verfügung stellt, damit ein Debugger den Programmablauf steuern und den Programmstatus prüfen kann.


0
2017-07-09 21:47