When did an expression last change value?¶
last [-f|-forward] [expression]¶
Jump to the last time in execution history when the value of expression was modified.
The expression is in the programming language of the program being debugged, for example, a variable:
99% 11,705> info locals x = 12 y = 45 z = 2025 99% 11,705> last x Searching backward for changes to 0x7fffffffd60c-0x7fffffffd610 for the expression: x Was = 12 Now = 0 0x000055555555527d in main () at simple.c:38 38 x = add(2); 97% 11,414> info locals x = 0 y = 0 z = 32767The first time an expression is given to this command, UDB remembers the location in memory of the data referred to by the expression, and then subsequent last commands with no expression execute the program until that location changes:
97% 11,414> last Searching backward for changes to 0x7fffffffd60c-0x7fffffffd610 without re-evaluating the expression: x Not modified. 97% 11,414> info locals x = 0 y = 0 z = 32767In the example above, the output “Not modified” means that there are no further previous changes to the variable. Because of that, the time in execution history (as displayed by the prompt) didn’t change.
If you input a blank line (typing just the return key), UDB repeats the latest command, if safe to do so. If the last command was last, this continues the search for changes to the value of the expression:
99% 11,705> last x Searching backward for changes to 0x7fffffffd60c-0x7fffffffd610 for the expression: x Was = 12 Now = 0 0x000055555555527d in main () at simple.c:38 38 x = add(2); 97% 11,414> Searching backward for changes to 0x7fffffffd60c-0x7fffffffd610 without re-evaluating the expression: x Not modified.
- -forward, -f¶
Jump to the next time the value will be modified (that is, search forward in execution history). For example:
97% 11,414> last -forward x Searching forward for changes to 0x7fffffffd60c-0x7fffffffd610 for the expression: x Was = 0 Now = 12 main () at simple.c:39 39 a++; 97% 11,414> Searching forward for changes to 0x7fffffffd60c-0x7fffffffd610 without re-evaluating the expression: x Not modified.As the last command works by evaluating the expression passed to it and then tracking the corresponding memory, it’s possible to follow changes to a value pointed to by a local variable even after the local variable goes out of scope.
For example, consider this simple program:
#include <stdio.h> #include <stdlib.h> static void increment_pointed_value(int *ptr_to_value_to_increment) { int old = *ptr_to_value_to_increment; *ptr_to_value_to_increment += 1; printf("Incremented value from %d to %d\n", old, *ptr_to_value_to_increment); } int main(void) { int *ptr = malloc(sizeof(int)); *ptr = 42; printf("Set the value to %d\n", *ptr); increment_pointed_value(ptr); free(ptr); return EXIT_SUCCESS; }If execution is stopped at the start of
increment_pointed_value()
, then using last on the value pointed to by local variableptr_to_value_to_increment
evaluates the expression so the command jumps to the calling function, even thoughptr_to_value_to_increment
is now out of scope:97% 11,368> last *ptr_to_value_to_increment Searching backward for changes to 0x5555555592a0-0x5555555592a4 for the expression: *ptr_to_value_to_increment Was = 42 Now = 0 0x00005555555551b9 in main () at malloc-var.c:20 20 *ptr = 42;The last command also reports last the memory corresponding to the tracked expression is allocated with
malloc()
oroperator new()
:96% 11,190> last Searching backward for changes to 0x5555555592a0-0x5555555592a4 without re-evaluating the expression: *ptr_to_value_to_increment The memory corresponding to this expression is allocated at this point in execution history. Use "last" with no expression to find earlier uses of this area of memory which don't correspond to the initial expression. This could help with memory corruption bugs.Similarly,
free()
andoperator delete()
on the tracked memory are reported:96% 11,190> last -forward Searching forward for changes to 0x5555555592a0-0x5555555592a4 without re-evaluating the expression: *ptr_to_value_to_increment Was = 0 Now = 42 main () at malloc-var.c:21 21 printf("Set the value to %d\n", *ptr); 96% 11,190> Searching forward for changes to 0x5555555592a0-0x5555555592a4 without re-evaluating the expression: *ptr_to_value_to_increment Was = 42 Now = 43 increment_pointed_value (ptr_to_value_to_increment=0x5555555592a0) at malloc-var.c:12 12 printf("Incremented value from %d to %d\n", old, *ptr_to_value_to_increment); 97% 11,368> Searching forward for changes to 0x5555555592a0-0x5555555592a4 without re-evaluating the expression: *ptr_to_value_to_increment The memory corresponding to this expression is freed at this point in. execution history. Use "last -f" with no expression to find later uses of this area of memory which don't correspond to the initial expression. This could help with use-after-free bugs.Warning
The last command relies on the value being tracked remaining at the same location in memory. For example, if vec is a std::vector<int>, then
last vec[3]
tracks the memory used by the fourth element in the vector, but if the vector is resized then (depending on how it is implemented) it may reallocate the underlying buffer of elements and so move the element to a new location in memory. If this happens, the last command does not track the element in its new location.Note
If the last command is used on a bitfield, then it will produce false matches in case of changes to the memory between the first byte used by the bitfield and the first byte plus the size of the bitfield type. For example, if a field has the type
uint64_t v : 4
then, despitev
using less than a byte,last
will stop for changes in the address range between v and v + sizeof(uint64_t).Note
The last command doesn’t support anonymous types, like structures that don’t have a name or type definition. For example,
last var
, where var is declared asstruct { int value; } var
, produces an error.New in version 6.9.