[PATCH] Add a new format letter to dump instructions backward

Toshihito Kikuchi
2016-01-24T21:31:42Z

[-- Attachment #1: Type: text/plain, Size: 2017 bytes --]

Hi all,

With the attached patch, I'd like to add a new format letter "j" to
the "x" command to dump instructions in backward direction.
This feature is basically equal to "ub" command in Windows debugger.

Below is the sample output:

(gdb) disas main
Dump of assembler code for function main(int, char**):
0x4040a7 <+0>: push %rbp
0x4040a8 <+1>: mov %rsp,%rbp
0x4040ab <+4>: sub $0x10,%rsp
0x4040af <+8>: mov %edi,-0x4(%rbp)
0x4040b2 <+11>: mov %rsi,-0x10(%rbp)
=> 0x4040b6 <+15>: lea 0x116(%rip),%rdi # 0x4041d3
0x4040bd <+22>: callq 0x400e30 <puts@plt>
0x4040c2 <+27>: mov $0x0,%eax
0x4040c7 <+32>: leaveq
0x4040c8 <+33>: retq
End of assembler dump.
(gdb) x/6j $rip
0x4040a6 <previous_function(...)+119>: retq
0x4040a7 <main(int, char**)>: push %rbp
0x4040a8 <main(int, char**)+1>: mov %rsp,%rbp
0x4040ab <main(int, char**)+4>: sub $0x10,%rsp
0x4040af <main(int, char**)+8>: mov %edi,-0x4(%rbp)
0x4040b2 <main(int, char**)+11>: mov %rsi,-0x10(%rbp)
(gdb) x/2j
0x4040a2 <previous_function(...)+115>: add %rdx,%rax
0x4040a5 <previous_function(...)+118>: leaveq

Unlike forward disassemble, theoretically it's impossible to
solve a correct frame only using the code. In the example below, we cannot
decide whether the instruction before "pop" is "nop" or "mov".

<correct frame>
0x4045bb <+89>: eb b4 jmp 0x404571
0x4045bd <+91>: 90 nop
0x4045be <+92>: 5d pop %rbp
0x4045bf <+93>: c3 retq

<incorrect frame>
0x4045bc <+90>: b4 90 mov $0x90,%ah
0x4045be <+92>: 5d pop %rbp
0x4045bf <+93>: c3 retq

To solve this, the change takes the help of line number information.

I introduced a new function "disassemble_backward" which goes backward
by referring the start address of each line.If we go out of the symbol range, we cancel backtrack and dump
the smallest address with the message.

Tested on x86_64 GNU/Linux.

Thanks,
Toshihito

[-- Attachment #2: ChangeLog --]
[-- Type: application/octet-stream, Size: 286 bytes --]

gdb/ChangeLog:
2016-01-24 Toshihito Kikuchi <k.toshihito@yahoo.de>

* printcmd.c (disassemble_backward): New function.
(null_fprintf) New function.
(null_print_address) New function.
(do_examine): Use new function.
(_initialize_printcmd) Adjust new format letter.

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: gdb-ub.diff --]
[-- Type: text/x-patch, Size: 5209 bytes --]

diff --git a/gdb/printcmd.c b/gdb/printcmd.c
index f5c4211..a6cf141 100644
--- a/gdb/printcmd.c
+++ b/gdb/printcmd.c
@@ -47,6 +47,7 @@
#include "cli/cli-utils.h"
#include "format.h"
#include "source.h"
+#include "linespec.h"

#ifdef TUI
#include "tui/tui.h" /* For tui_active et al. */
@@ -785,6 +786,97 @@ print_address_demangle (const struct value_print_options *opts,
}
\f

+/* These empty functions are used in disassemble_backward to
+ suppress standard output during the call to gdbarch_print_insn. */
+
+static int
+null_fprintf (void *stream, const char *format ATTRIBUTE_UNUSED, ...)
+{
+ return 0;
+}
+
+static void
+null_print_address (bfd_vma addr, struct disassemble_info *info)
+{
+ return;
+}
+
+/* Rewind assembly from addr and return the start address after the given
+ number of lines are disassembled. To avoid disassembling in a wrong frame,
+ we get addresses in a correct frame using line information.
+ If we go out of the symbol range during disassembling, we return
+ the smallest address we've got so far. */
+
+static CORE_ADDR
+disassemble_backward(struct gdbarch *gdbarch, CORE_ADDR addr,
+ int count, int *linesread)
+{
+ char addrstr[64];
+ struct symtabs_and_lines sals;
+ CORE_ADDR start_pc, end_pc, ret;
+
+ start_pc = end_pc = ret = 0;
+ *linesread = 0;
+
+ sprintf (addrstr, "*%p", (void *) (addr - 1));
+ sals = decode_line_with_last_displayed (addrstr, DECODE_LINE_FUNFIRSTLINE);
+ if (sals.nelts >= 1)
+ {
+ if ((!sals.sals[0].line)
+ || (!find_line_pc_range (sals.sals[0], &start_pc, &end_pc)))
+ {
+ printf_filtered (_("No line number information available "
+ "for address "));
+ wrap_here (" ");
+ print_address (gdbarch, addr - 1, gdb_stdout);
+ printf_filtered ("\n");
+ start_pc = end_pc = 0;
+ }
+ xfree (sals.sals);
+ }
+
+ if (start_pc)
+ {
+ VEC (CORE_ADDR) *pcs = NULL;
+ struct disassemble_info di;
+ CORE_ADDR p;
+ int i;
+
+ VEC_reserve (CORE_ADDR, pcs, count);
+
+ di = gdb_disassemble_info (gdbarch, NULL);
+ di.fprintf_func = null_fprintf;
+ di.print_address_func = null_print_address;
+
+ p = start_pc;
+ for (i = 0; p < addr; ++i)
+ {
+ VEC_safe_push (CORE_ADDR, pcs, p);
+ p += gdbarch_print_insn (gdbarch, (CORE_ADDR)p, &di);
+ }
+
+ if (i >= count)
+ {
+ ret = VEC_index (CORE_ADDR, pcs, i - count);
+ *linesread = count;
+ }
+ else
+ {
+ int linesread_internal = 0;
+ ret = disassemble_backward (gdbarch, VEC_index (CORE_ADDR, pcs, 0),
+ count - i, &linesread_internal);
+ *linesread = i + linesread_internal;
+
+ /* Return the smallest valid address we've got
+ if the recursive call above failed. */
+ if (!ret)
+ ret = VEC_index (CORE_ADDR, pcs, 0);
+ }
+ VEC_free (CORE_ADDR, pcs);
+ }
+ return ret;
+}
+
/* Examine data at address ADDR in format FMT.
Fetch it from memory and print on gdb_stdout. */

@@ -798,6 +890,7 @@ do_examine (struct format_data fmt, struct gdbarch *gdbarch, CORE_ADDR addr)
int i;
int maxelts;
struct value_print_options opts;
+ CORE_ADDR addr_rewound = 0;

format = fmt.format;
size = fmt.size;
@@ -805,6 +898,24 @@ do_examine (struct format_data fmt, struct gdbarch *gdbarch, CORE_ADDR addr)
next_gdbarch = gdbarch;
next_address = addr;

+ if (format == 'j')
+ {
+ /* If 'j' is given, we get the address after going back
+ by 'count' instructions from addr and dump instructions
+ from it by setting format to 'i'.
+ At the end of this function, we adjust next_address
+ for subsequent command calls. */
+ int linesread = 0;
+ addr_rewound = next_address = disassemble_backward (gdbarch,
+ addr, count, &linesread);
+ if (!next_address)
+ {
+ return;
+ }
+ count = linesread;
+ format = 'i';
+ }
+
/* Instruction format implies fetch single bytes
regardless of the specified size.
The case of strings is handled in decode_format, only explicit
@@ -913,6 +1024,9 @@ do_examine (struct format_data fmt, struct gdbarch *gdbarch, CORE_ADDR addr)
printf_filtered ("\n");
gdb_flush (gdb_stdout);
}
+
+ if (addr_rewound)
+ next_address = addr_rewound;
}
\f
static void
@@ -2518,8 +2632,8 @@ Examine memory: x/FMT ADDRESS.\n\
ADDRESS is an expression for the memory address to examine.\n\
FMT is a repeat count followed by a format letter and a size letter.\n\
Format letters are o(octal), x(hex), d(decimal), u(unsigned decimal),\n\
- t(binary), f(float), a(address), i(instruction), c(char), s(string)\n\
- and z(hex, zero padded on the left).\n\
+ t(binary), f(float), a(address), i(instruction), j(instruction backward),\n\
+ c(char), s(string) and z(hex, zero padded on the left).\n\
Size letters are b(byte), h(halfword), w(word), g(giant, 8 bytes).\n\
The specified number of objects of the specified size are printed\n\
according to the format.\n\n\