diff --git a/src/ExprNode.cc b/src/ExprNode.cc
index 91ca9360e34af3f6e2a602ffaac0654375b4cc3e..c88c36be39b3b16772a7074038c305c43a9aa8f3 100644
--- a/src/ExprNode.cc
+++ b/src/ExprNode.cc
@@ -8927,3 +8927,11 @@ ExprNode::matchLinearCombinationOfEndogenousWithConstant() const
       }
   return { endo_terms, intercept };
 }
+
+string
+ExprNode::toString() const
+{
+  ostringstream ss;
+  writeJsonOutput(ss, {}, {});
+  return ss.str();
+}
diff --git a/src/ExprNode.hh b/src/ExprNode.hh
index c4b20990cb2c1088c073dd0273c9ba6908815ade..a2ffa392ddfdd81f7a769b9de195752ea2374774 100644
--- a/src/ExprNode.hh
+++ b/src/ExprNode.hh
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2007-2021 Dynare Team
+ * Copyright © 2007-2022 Dynare Team
  *
  * This file is part of Dynare.
  *
@@ -341,6 +341,9 @@ public:
   //! Writes output of node in JSON syntax
   virtual void writeJsonOutput(ostream &output, const temporary_terms_t &temporary_terms, const deriv_node_temp_terms_t &tef_terms, bool isdynamic = true) const = 0;
 
+  // Returns a string representation of the expression, used by the GDB pretty printer
+  string toString() const;
+
   //! Writes the Abstract Syntax Tree in JSON
   virtual void writeJsonAST(ostream &output) const = 0;
 
diff --git a/src/Makefile.am b/src/Makefile.am
index bf8c8272bc14f0074fac9e5a19730543315aa2d2..43dc2b2bd7b1bdb2e2255d43f21a67221340d6ab 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -62,6 +62,7 @@ ACLOCAL_AMFLAGS = -I m4
 
 EXTRA_DIST = \
 	Doxyfile \
+	dynare-preprocessor-gdb.py \
 	$(BUILT_SOURCES)
 
 # The -I. is for <FlexLexer.h>
diff --git a/src/dynare-preprocessor-gdb.py b/src/dynare-preprocessor-gdb.py
new file mode 100644
index 0000000000000000000000000000000000000000..4c74e6ed38884b327afcd0a0d5825c6570acb6a2
--- /dev/null
+++ b/src/dynare-preprocessor-gdb.py
@@ -0,0 +1,52 @@
+# GDB pretty-printer for ExprNode class hierarchy
+
+# Copyright © 2022 Dynare Team
+#
+# This file is part of Dynare.
+#
+# Dynare 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.
+#
+# Dynare 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 Dynare.  If not, see <https://www.gnu.org/licenses/>.
+
+class ExprNodePrinter:
+    '''Pretty-prints an ExprNode value'''
+    def __init__(self, val):
+        self.val = val
+
+    def to_string(self):
+        # Call the toString() method on the pointer.
+        # We must use the raw pretty-printer for the pointer itself, otherwise
+        # we enter an infinite loop.
+        # We retrieve a C string, because with C++ strings the gdb pretty-printer
+        # insists on keeping the quotes around it.
+        r = gdb.parse_and_eval("((ExprNode *) " + self.val.format_string(raw = True) + ")->toString().c_str()")
+        typestr = "(" + str(self.val.type) + ") ";
+        # Add dynamic type information between brackets, if different from static type
+        if str(self.val.type) != str(self.val.dynamic_type):
+            typestr += "[" + str(self.val.dynamic_type) + "] "
+        return typestr + r.string()
+
+class ExprNodePrinterControl(gdb.printing.PrettyPrinter):
+    '''Determines whether a value can be pretty printed with ExprNodePrinter. To be directly registered within the GDB API.'''
+    def __init__(self):
+        # The name below will appear in “info pretty-printer”, and can be used with “enable/disable pretty-printer”
+        super().__init__('ExprNode')
+
+    def __call__(self, val):
+        # Check if the value is a subtype of ExprNode *.
+        # Doing a dynamic_cast on a non-pointer type triggers an exception, so we first check
+        # whethe it’s a pointer (after resolving for typedefs, such as “expr_t”).
+        if val.type.strip_typedefs().code == gdb.TYPE_CODE_PTR and val.dynamic_cast(gdb.lookup_type('ExprNode').pointer()) != 0:
+            return ExprNodePrinter(val)
+
+# Register the pretty printer
+gdb.pretty_printers.append(ExprNodePrinterControl())