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% 13,519> info locals
x = 12
y = 45
z = 2025
99% 13,519> last x
Searching backward for changes to 0x7fffffffdcdc-0x7fffffffdce0 for the
expression:
  x

Old value = 12
New value = 0
0x0000555555555293 in main () at simple.c:38
38          x = add(2);
97% 13,229> info locals
x = 0
y = 0
z = 32767

The 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% 13,229> last
Searching backward for changes to 0x7fffffffdcdc-0x7fffffffdce0 without
re-evaluating the expression:
  x

Not modified.
97% 13,229> info locals
x = 0
y = 0
z = 32767

In 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% 13,519> last x
Searching backward for changes to 0x7fffffffdcdc-0x7fffffffdce0 for the
expression:
  x

Old value = 12
New value = 0
0x0000555555555293 in main () at simple.c:38
38          x = add(2);
97% 13,229>
Searching backward for changes to 0x7fffffffdcdc-0x7fffffffdce0 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% 13,229> last -forward x
Searching forward for changes to 0x7fffffffdcdc-0x7fffffffdce0 for the
expression:
  x

Old value = 0
New value = 12
main () at simple.c:39
39          a++;
97% 13,229>
Searching forward for changes to 0x7fffffffdcdc-0x7fffffffdce0 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 variable ptr_to_value_to_increment evaluates the expression so the command jumps to the calling function, even if ptr_to_value_to_increment is now out of scope:

98% 13,212> last *ptr_to_value_to_increment
Searching backward for changes to 0x5555555592a0-0x5555555592a4 for the
expression:
  *ptr_to_value_to_increment

Old value = 42
New value = 0
0x00005555555551ee 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() or operator new():

97% 13,086> 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() and operator delete() on the tracked memory are reported:

97% 13,086> last -forward
Searching forward for changes to 0x5555555592a0-0x5555555592a4 without
re-evaluating the expression:
  *ptr_to_value_to_increment

Old value = 0
New value = 42
main () at malloc-var.c:21
21          printf("Set the value to %d\n", *ptr);
97% 13,086>
Searching forward for changes to 0x5555555592a0-0x5555555592a4 without
re-evaluating the expression:
  *ptr_to_value_to_increment

Old value = 42
New value = 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);
98% 13,212>
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

When using last without the -forward option, take care interpreting the terms “old value” and “new value” as they can be counter-intuitive when executing the program in reverse. The “old value” is the value before the last command was started, and “new value” is the value after it finished.

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 is uint64_t v : 4 then, despite v 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 typedef. For example, last var, where var is declared as struct { int value; } var, produces an error.

New in version 6.9.