2010-06-28 Phil Muldoon <pmuldoon@redhat.com>
[deliverable/binutils-gdb.git] / gdb / python / py-frame.c
CommitLineData
f8f6f20b
TJB
1/* Python interface to stack frames
2
4c38e0a4 3 Copyright (C) 2008, 2009, 2010 Free Software Foundation, Inc.
f8f6f20b
TJB
4
5 This file is part of GDB.
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>. */
19
20#include "defs.h"
21#include "charset.h"
22#include "block.h"
23#include "frame.h"
24#include "exceptions.h"
25#include "symtab.h"
26#include "stack.h"
27#include "value.h"
28#include "python-internal.h"
f3e9a817
PM
29#include "symfile.h"
30#include "objfiles.h"
f8f6f20b
TJB
31
32typedef struct {
33 PyObject_HEAD
34 struct frame_id frame_id;
35 struct gdbarch *gdbarch;
36
37 /* Marks that the FRAME_ID member actually holds the ID of the frame next
38 to this, and not this frames' ID itself. This is a hack to permit Python
39 frame objects which represent invalid frames (i.e., the last frame_info
40 in a corrupt stack). The problem arises from the fact that this code
41 relies on FRAME_ID to uniquely identify a frame, which is not always true
42 for the last "frame" in a corrupt stack (it can have a null ID, or the same
43 ID as the previous frame). Whenever get_prev_frame returns NULL, we
44 record the frame_id of the next frame and set FRAME_ID_IS_NEXT to 1. */
45 int frame_id_is_next;
46} frame_object;
47
48/* Require a valid frame. This must be called inside a TRY_CATCH, or
49 another context in which a gdb exception is allowed. */
50#define FRAPY_REQUIRE_VALID(frame_obj, frame) \
51 do { \
52 frame = frame_object_to_frame_info (frame_obj); \
53 if (frame == NULL) \
044c0f87 54 error (_("Frame is invalid.")); \
f8f6f20b
TJB
55 } while (0)
56
57static PyTypeObject frame_object_type;
58
59/* Returns the frame_info object corresponding to the given Python Frame
60 object. If the frame doesn't exist anymore (the frame id doesn't
61 correspond to any frame in the inferior), returns NULL. */
62
63static struct frame_info *
64frame_object_to_frame_info (frame_object *frame_obj)
65{
66 struct frame_info *frame;
67
68 frame = frame_find_by_id (frame_obj->frame_id);
69 if (frame == NULL)
70 return NULL;
71
72 if (frame_obj->frame_id_is_next)
73 frame = get_prev_frame (frame);
74
75 return frame;
76}
77
78/* Called by the Python interpreter to obtain string representation
79 of the object. */
80
81static PyObject *
82frapy_str (PyObject *self)
83{
84 char *s;
f8f6f20b
TJB
85 PyObject *result;
86 struct ui_file *strfile;
87
88 strfile = mem_fileopen ();
89 fprint_frame_id (strfile, ((frame_object *) self)->frame_id);
759ef836 90 s = ui_file_xstrdup (strfile, NULL);
f8f6f20b
TJB
91 result = PyString_FromString (s);
92 xfree (s);
93
94 return result;
95}
96
97/* Implementation of gdb.Frame.is_valid (self) -> Boolean.
98 Returns True if the frame corresponding to the frame_id of this
99 object still exists in the inferior. */
100
101static PyObject *
102frapy_is_valid (PyObject *self, PyObject *args)
103{
104 struct frame_info *frame;
105
106 frame = frame_object_to_frame_info ((frame_object *) self);
107 if (frame == NULL)
108 Py_RETURN_FALSE;
109
110 Py_RETURN_TRUE;
111}
112
113/* Implementation of gdb.Frame.name (self) -> String.
114 Returns the name of the function corresponding to this frame. */
115
116static PyObject *
117frapy_name (PyObject *self, PyObject *args)
118{
119 struct frame_info *frame;
120 char *name;
121 enum language lang;
122 PyObject *result;
123 volatile struct gdb_exception except;
124
125 TRY_CATCH (except, RETURN_MASK_ALL)
126 {
127 FRAPY_REQUIRE_VALID ((frame_object *) self, frame);
128
129 find_frame_funname (frame, &name, &lang);
130 }
131 GDB_PY_HANDLE_EXCEPTION (except);
132
133 if (name)
134 result = PyUnicode_Decode (name, strlen (name), host_charset (), NULL);
135 else
136 {
137 result = Py_None;
138 Py_INCREF (Py_None);
139 }
140
141 return result;
142}
143
144/* Implementation of gdb.Frame.type (self) -> Integer.
145 Returns the frame type, namely one of the gdb.*_FRAME constants. */
146
147static PyObject *
148frapy_type (PyObject *self, PyObject *args)
149{
150 struct frame_info *frame;
151 enum frame_type type = NORMAL_FRAME;/* Initialize to appease gcc warning. */
152 volatile struct gdb_exception except;
153
154 TRY_CATCH (except, RETURN_MASK_ALL)
155 {
156 FRAPY_REQUIRE_VALID ((frame_object *) self, frame);
157
158 type = get_frame_type (frame);
159 }
160 GDB_PY_HANDLE_EXCEPTION (except);
161
162 return PyInt_FromLong (type);
163}
164
165/* Implementation of gdb.Frame.unwind_stop_reason (self) -> Integer.
166 Returns one of the gdb.FRAME_UNWIND_* constants. */
167
168static PyObject *
169frapy_unwind_stop_reason (PyObject *self, PyObject *args)
170{
171 struct frame_info *frame = NULL; /* Initialize to appease gcc warning. */
172 volatile struct gdb_exception except;
173 enum unwind_stop_reason stop_reason;
174
175 TRY_CATCH (except, RETURN_MASK_ALL)
176 {
177 FRAPY_REQUIRE_VALID ((frame_object *) self, frame);
178 }
179 GDB_PY_HANDLE_EXCEPTION (except);
180
181 stop_reason = get_frame_unwind_stop_reason (frame);
182
183 return PyInt_FromLong (stop_reason);
184}
185
186/* Implementation of gdb.Frame.pc (self) -> Long.
187 Returns the frame's resume address. */
188
189static PyObject *
190frapy_pc (PyObject *self, PyObject *args)
191{
192 CORE_ADDR pc = 0; /* Initialize to appease gcc warning. */
193 struct frame_info *frame;
194 volatile struct gdb_exception except;
195
196 TRY_CATCH (except, RETURN_MASK_ALL)
197 {
198 FRAPY_REQUIRE_VALID ((frame_object *) self, frame);
199
200 pc = get_frame_pc (frame);
201 }
202 GDB_PY_HANDLE_EXCEPTION (except);
203
204 return PyLong_FromUnsignedLongLong (pc);
205}
206
f3e9a817
PM
207/* Implementation of gdb.Frame.block (self) -> gdb.Block.
208 Returns the frame's code block. */
209
210static PyObject *
211frapy_block (PyObject *self, PyObject *args)
212{
213 struct frame_info *frame;
214 struct block *block = NULL;
215 volatile struct gdb_exception except;
216 struct symtab_and_line sal;
217
218 TRY_CATCH (except, RETURN_MASK_ALL)
219 {
220 FRAPY_REQUIRE_VALID ((frame_object *) self, frame);
221
222 find_frame_sal (frame, &sal);
223 block = block_for_pc (get_frame_address_in_block (frame));
224 }
225 GDB_PY_HANDLE_EXCEPTION (except);
226
227 if (!sal.symtab || !sal.symtab->objfile)
228 {
229 PyErr_SetString (PyExc_RuntimeError,
044c0f87 230 _("Cannot locate object file for block."));
f3e9a817
PM
231 return NULL;
232 }
233
234 if (block)
235 return block_to_block_object (block, sal.symtab->objfile);
236
237 Py_RETURN_NONE;
238}
239
240
241/* Implementation of gdb.Frame.function (self) -> gdb.Symbol.
242 Returns the symbol for the function corresponding to this frame. */
243
244static PyObject *
245frapy_function (PyObject *self, PyObject *args)
246{
247 struct symbol *sym = NULL;
248 struct frame_info *frame;
249 volatile struct gdb_exception except;
250
251 TRY_CATCH (except, RETURN_MASK_ALL)
252 {
253 FRAPY_REQUIRE_VALID ((frame_object *) self, frame);
254
255 sym = find_pc_function (get_frame_address_in_block (frame));
256 }
257 GDB_PY_HANDLE_EXCEPTION (except);
258
259 if (sym)
260 return symbol_to_symbol_object (sym);
261
262 Py_RETURN_NONE;
263}
264
f8f6f20b
TJB
265/* Convert a frame_info struct to a Python Frame object.
266 Sets a Python exception and returns NULL on error. */
267
595939de 268PyObject *
f8f6f20b
TJB
269frame_info_to_frame_object (struct frame_info *frame)
270{
271 frame_object *frame_obj;
272
273 frame_obj = PyObject_New (frame_object, &frame_object_type);
274 if (frame_obj == NULL)
275 {
044c0f87
PM
276 PyErr_SetString (PyExc_MemoryError,
277 _("Could not allocate frame object."));
f8f6f20b
TJB
278 return NULL;
279 }
280
281 /* Try to get the previous frame, to determine if this is the last frame
282 in a corrupt stack. If so, we need to store the frame_id of the next
283 frame and not of this one (which is possibly invalid). */
284 if (get_prev_frame (frame) == NULL
285 && get_frame_unwind_stop_reason (frame) != UNWIND_NO_REASON
286 && get_next_frame (frame) != NULL)
287 {
288 frame_obj->frame_id = get_frame_id (get_next_frame (frame));
289 frame_obj->frame_id_is_next = 1;
290 }
291 else
292 {
293 frame_obj->frame_id = get_frame_id (frame);
294 frame_obj->frame_id_is_next = 0;
295 }
296
297 frame_obj->gdbarch = get_frame_arch (frame);
298
595939de 299 return (PyObject *) frame_obj;
f8f6f20b
TJB
300}
301
302/* Implementation of gdb.Frame.older (self) -> gdb.Frame.
303 Returns the frame immediately older (outer) to this frame, or None if
304 there isn't one. */
305
306static PyObject *
307frapy_older (PyObject *self, PyObject *args)
308{
309 struct frame_info *frame, *prev;
310 volatile struct gdb_exception except;
311 PyObject *prev_obj = NULL; /* Initialize to appease gcc warning. */
312
313 TRY_CATCH (except, RETURN_MASK_ALL)
314 {
315 FRAPY_REQUIRE_VALID ((frame_object *) self, frame);
316
317 prev = get_prev_frame (frame);
318 if (prev)
319 prev_obj = (PyObject *) frame_info_to_frame_object (prev);
320 else
321 {
322 Py_INCREF (Py_None);
323 prev_obj = Py_None;
324 }
325 }
326 GDB_PY_HANDLE_EXCEPTION (except);
327
328 return prev_obj;
329}
330
331/* Implementation of gdb.Frame.newer (self) -> gdb.Frame.
332 Returns the frame immediately newer (inner) to this frame, or None if
333 there isn't one. */
334
335static PyObject *
336frapy_newer (PyObject *self, PyObject *args)
337{
338 struct frame_info *frame, *next;
339 volatile struct gdb_exception except;
340 PyObject *next_obj = NULL; /* Initialize to appease gcc warning. */
341
342 TRY_CATCH (except, RETURN_MASK_ALL)
343 {
344 FRAPY_REQUIRE_VALID ((frame_object *) self, frame);
345
346 next = get_next_frame (frame);
347 if (next)
348 next_obj = (PyObject *) frame_info_to_frame_object (next);
349 else
350 {
351 Py_INCREF (Py_None);
352 next_obj = Py_None;
353 }
354 }
355 GDB_PY_HANDLE_EXCEPTION (except);
356
357 return next_obj;
358}
359
f3e9a817
PM
360/* Implementation of gdb.Frame.find_sal (self) -> gdb.Symtab_and_line.
361 Returns the frame's symtab and line. */
362
363static PyObject *
364frapy_find_sal (PyObject *self, PyObject *args)
365{
366 struct frame_info *frame;
367 struct symtab_and_line sal;
f3e9a817
PM
368 volatile struct gdb_exception except;
369 PyObject *sal_obj = NULL; /* Initialize to appease gcc warning. */
370
371 TRY_CATCH (except, RETURN_MASK_ALL)
372 {
373 FRAPY_REQUIRE_VALID ((frame_object *) self, frame);
374
375 find_frame_sal (frame, &sal);
376 sal_obj = symtab_and_line_to_sal_object (sal);
377 }
378 GDB_PY_HANDLE_EXCEPTION (except);
379
380 return sal_obj;
381}
382
dc00d89f
PM
383/* Implementation of gdb.Frame.read_var_value (self, variable,
384 [block]) -> gdb.Value. If the optional block argument is provided
385 start the search from that block, otherwise search from the frame's
386 current block (determined by examining the resume address of the
387 frame). The variable argument must be a string or an instance of a
388 gdb.Symbol. The block argument must be an instance of gdb.Block. */
f8f6f20b
TJB
389static PyObject *
390frapy_read_var (PyObject *self, PyObject *args)
391{
392 struct frame_info *frame;
dc00d89f 393 PyObject *sym_obj, *block_obj = NULL;
f8f6f20b
TJB
394 struct symbol *var = NULL; /* gcc-4.3.2 false warning. */
395 struct value *val = NULL;
396 volatile struct gdb_exception except;
397
dc00d89f 398 if (!PyArg_ParseTuple (args, "O|O", &sym_obj, &block_obj))
f8f6f20b
TJB
399 return NULL;
400
f3e9a817
PM
401 if (PyObject_TypeCheck (sym_obj, &symbol_object_type))
402 var = symbol_object_to_symbol (sym_obj);
403 else if (gdbpy_is_string (sym_obj))
f8f6f20b
TJB
404 {
405 char *var_name;
406 struct block *block = NULL;
407 struct cleanup *cleanup;
408 volatile struct gdb_exception except;
409
410 var_name = python_string_to_target_string (sym_obj);
411 if (!var_name)
412 return NULL;
413 cleanup = make_cleanup (xfree, var_name);
414
dc00d89f
PM
415 if (block_obj)
416 {
417 block = block_object_to_block (block_obj);
418 if (!block)
419 {
420 PyErr_SetString (PyExc_RuntimeError,
421 _("Second argument must be block."));
422 return NULL;
423 }
424 }
425
f8f6f20b
TJB
426 TRY_CATCH (except, RETURN_MASK_ALL)
427 {
428 FRAPY_REQUIRE_VALID ((frame_object *) self, frame);
429
dc00d89f
PM
430 if (!block)
431 block = block_for_pc (get_frame_address_in_block (frame));
f8f6f20b
TJB
432 var = lookup_symbol (var_name, block, VAR_DOMAIN, NULL);
433 }
434 GDB_PY_HANDLE_EXCEPTION (except);
435
436 if (!var)
437 {
438 PyErr_Format (PyExc_ValueError,
044c0f87 439 _("Variable '%s' not found."), var_name);
f8f6f20b
TJB
440 do_cleanups (cleanup);
441
442 return NULL;
443 }
444
445 do_cleanups (cleanup);
446 }
447 else
448 {
449 PyErr_SetString (PyExc_TypeError,
044c0f87 450 _("Argument must be a symbol or string."));
f8f6f20b
TJB
451 return NULL;
452 }
453
454 TRY_CATCH (except, RETURN_MASK_ALL)
455 {
456 FRAPY_REQUIRE_VALID ((frame_object *) self, frame);
457
458 val = read_var_value (var, frame);
459 }
460 GDB_PY_HANDLE_EXCEPTION (except);
461
dc00d89f
PM
462 if (!val)
463 {
464 PyErr_Format (PyExc_ValueError,
465 _("Variable cannot be found for symbol '%s'."),
466 SYMBOL_NATURAL_NAME (var));
467 return NULL;
468 }
f8f6f20b 469
dc00d89f 470 return value_to_value_object (val);
f8f6f20b
TJB
471}
472
f3e9a817
PM
473/* Select this frame. */
474
475static PyObject *
476frapy_select (PyObject *self, PyObject *args)
477{
478 struct frame_info *fi;
479 frame_object *frame = (frame_object *) self;
480 volatile struct gdb_exception except;
481
482 TRY_CATCH (except, RETURN_MASK_ALL)
483 {
484 FRAPY_REQUIRE_VALID (frame, fi);
485
486 select_frame (fi);
487 }
488 GDB_PY_HANDLE_EXCEPTION (except);
489
490 Py_RETURN_NONE;
491}
492
f8f6f20b
TJB
493/* Implementation of gdb.selected_frame () -> gdb.Frame.
494 Returns the selected frame object. */
495
496PyObject *
497gdbpy_selected_frame (PyObject *self, PyObject *args)
498{
499 struct frame_info *frame;
595939de 500 PyObject *frame_obj = NULL; /* Initialize to appease gcc warning. */
f8f6f20b
TJB
501 volatile struct gdb_exception except;
502
503 TRY_CATCH (except, RETURN_MASK_ALL)
504 {
505 frame = get_selected_frame ("No frame is currently selected.");
506 frame_obj = frame_info_to_frame_object (frame);
507 }
508 GDB_PY_HANDLE_EXCEPTION (except);
509
595939de 510 return frame_obj;
f8f6f20b
TJB
511}
512
513/* Implementation of gdb.stop_reason_string (Integer) -> String.
514 Return a string explaining the unwind stop reason. */
515
516PyObject *
517gdbpy_frame_stop_reason_string (PyObject *self, PyObject *args)
518{
519 int reason;
520 const char *str;
521
522 if (!PyArg_ParseTuple (args, "i", &reason))
523 return NULL;
524
525 if (reason < 0 || reason > UNWIND_NO_SAVED_PC)
526 {
044c0f87
PM
527 PyErr_SetString (PyExc_ValueError,
528 _("Invalid frame stop reason."));
f8f6f20b
TJB
529 return NULL;
530 }
531
532 str = frame_stop_reason_string (reason);
533 return PyUnicode_Decode (str, strlen (str), host_charset (), NULL);
534}
535
536/* Implements the equality comparison for Frame objects.
537 All other comparison operators will throw a TypeError Python exception,
538 as they aren't valid for frames. */
539
540static PyObject *
541frapy_richcompare (PyObject *self, PyObject *other, int op)
542{
18e8c3bc
TT
543 int result;
544
545 if (!PyObject_TypeCheck (other, &frame_object_type)
546 || (op != Py_EQ && op != Py_NE))
f8f6f20b 547 {
18e8c3bc
TT
548 Py_INCREF (Py_NotImplemented);
549 return Py_NotImplemented;
f8f6f20b
TJB
550 }
551
552 if (frame_id_eq (((frame_object *) self)->frame_id,
553 ((frame_object *) other)->frame_id))
18e8c3bc
TT
554 result = Py_EQ;
555 else
556 result = Py_NE;
f8f6f20b 557
18e8c3bc
TT
558 if (op == result)
559 Py_RETURN_TRUE;
f8f6f20b
TJB
560 Py_RETURN_FALSE;
561}
562
563/* Sets up the Frame API in the gdb module. */
564
565void
566gdbpy_initialize_frames (void)
567{
568 if (PyType_Ready (&frame_object_type) < 0)
569 return;
570
571 /* Note: These would probably be best exposed as class attributes of Frame,
572 but I don't know how to do it except by messing with the type's dictionary.
573 That seems too messy. */
574 PyModule_AddIntConstant (gdb_module, "NORMAL_FRAME", NORMAL_FRAME);
575 PyModule_AddIntConstant (gdb_module, "DUMMY_FRAME", DUMMY_FRAME);
576 PyModule_AddIntConstant (gdb_module, "SIGTRAMP_FRAME", SIGTRAMP_FRAME);
577 PyModule_AddIntConstant (gdb_module, "SENTINEL_FRAME", SENTINEL_FRAME);
578 PyModule_AddIntConstant (gdb_module,
579 "FRAME_UNWIND_NO_REASON", UNWIND_NO_REASON);
580 PyModule_AddIntConstant (gdb_module,
581 "FRAME_UNWIND_NULL_ID", UNWIND_NULL_ID);
582 PyModule_AddIntConstant (gdb_module,
583 "FRAME_UNWIND_FIRST_ERROR", UNWIND_FIRST_ERROR);
584 PyModule_AddIntConstant (gdb_module,
585 "FRAME_UNWIND_INNER_ID", UNWIND_INNER_ID);
586 PyModule_AddIntConstant (gdb_module,
587 "FRAME_UNWIND_SAME_ID", UNWIND_SAME_ID);
588 PyModule_AddIntConstant (gdb_module,
589 "FRAME_UNWIND_NO_SAVED_PC", UNWIND_NO_SAVED_PC);
590
591 Py_INCREF (&frame_object_type);
592 PyModule_AddObject (gdb_module, "Frame", (PyObject *) &frame_object_type);
593}
594
595\f
596
597static PyMethodDef frame_object_methods[] = {
598 { "is_valid", frapy_is_valid, METH_NOARGS,
599 "is_valid () -> Boolean.\n\
600Return true if this frame is valid, false if not." },
601 { "name", frapy_name, METH_NOARGS,
602 "name () -> String.\n\
603Return the function name of the frame, or None if it can't be determined." },
604 { "type", frapy_type, METH_NOARGS,
605 "type () -> Integer.\n\
606Return the type of the frame." },
607 { "unwind_stop_reason", frapy_unwind_stop_reason, METH_NOARGS,
608 "unwind_stop_reason () -> Integer.\n\
609Return the reason why it's not possible to find frames older than this." },
610 { "pc", frapy_pc, METH_NOARGS,
611 "pc () -> Long.\n\
612Return the frame's resume address." },
f3e9a817
PM
613 { "block", frapy_block, METH_NOARGS,
614 "block () -> gdb.Block.\n\
615Return the frame's code block." },
616 { "function", frapy_function, METH_NOARGS,
617 "function () -> gdb.Symbol.\n\
618Returns the symbol for the function corresponding to this frame." },
f8f6f20b
TJB
619 { "older", frapy_older, METH_NOARGS,
620 "older () -> gdb.Frame.\n\
621Return the frame that called this frame." },
622 { "newer", frapy_newer, METH_NOARGS,
623 "newer () -> gdb.Frame.\n\
624Return the frame called by this frame." },
f3e9a817
PM
625 { "find_sal", frapy_find_sal, METH_NOARGS,
626 "find_sal () -> gdb.Symtab_and_line.\n\
627Return the frame's symtab and line." },
f8f6f20b
TJB
628 { "read_var", frapy_read_var, METH_VARARGS,
629 "read_var (variable) -> gdb.Value.\n\
630Return the value of the variable in this frame." },
f3e9a817
PM
631 { "select", frapy_select, METH_NOARGS,
632 "Select this frame as the user's current frame." },
f8f6f20b
TJB
633 {NULL} /* Sentinel */
634};
635
636static PyTypeObject frame_object_type = {
637 PyObject_HEAD_INIT (NULL)
638 0, /* ob_size */
639 "gdb.Frame", /* tp_name */
640 sizeof (frame_object), /* tp_basicsize */
641 0, /* tp_itemsize */
642 0, /* tp_dealloc */
643 0, /* tp_print */
644 0, /* tp_getattr */
645 0, /* tp_setattr */
646 0, /* tp_compare */
647 0, /* tp_repr */
648 0, /* tp_as_number */
649 0, /* tp_as_sequence */
650 0, /* tp_as_mapping */
651 0, /* tp_hash */
652 0, /* tp_call */
653 frapy_str, /* tp_str */
654 0, /* tp_getattro */
655 0, /* tp_setattro */
656 0, /* tp_as_buffer */
657 Py_TPFLAGS_DEFAULT, /* tp_flags */
658 "GDB frame object", /* tp_doc */
659 0, /* tp_traverse */
660 0, /* tp_clear */
661 frapy_richcompare, /* tp_richcompare */
662 0, /* tp_weaklistoffset */
663 0, /* tp_iter */
664 0, /* tp_iternext */
665 frame_object_methods, /* tp_methods */
666 0, /* tp_members */
667 0, /* tp_getset */
668 0, /* tp_base */
669 0, /* tp_dict */
670 0, /* tp_descr_get */
671 0, /* tp_descr_set */
672 0, /* tp_dictoffset */
673 0, /* tp_init */
674 0, /* tp_alloc */
675 PyType_GenericNew /* tp_new */
676};
This page took 0.226632 seconds and 4 git commands to generate.