* [PATCH] gdb/arm: avoid undefined behavior shift when decoding immediate value
@ 2020-11-09 20:49 Simon Marchi
2020-11-11 10:00 ` Alan Hayward
2020-11-13 22:35 ` Luis Machado
0 siblings, 2 replies; 6+ messages in thread
From: Simon Marchi @ 2020-11-09 20:49 UTC (permalink / raw)
To: gdb-patches
From: Simon Marchi <simon.marchi@polymtl.ca>
When loading the code file provided in PR 26828 and GDB is build with
UBSan, we get:
Core was generated by `./Foo'.
Program terminated with signal SIGABRT, Aborted.
#0 0xb6c3809c in pthread_cond_wait () from /home/simark/build/binutils-gdb/gdb/repo/lib/libpthread.so.0
[Current thread is 1 (LWP 29367)]
(gdb) bt
/home/simark/src/binutils-gdb/gdb/arm-tdep.c:1551:30: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
The sequence of instructions at pthread_cond_wait, in the
libpthread.so.0 library, contains this instruction with an immediate
constant with a "rotate amount" of 0:
e24dd044 sub sp, sp, #68 ; 0x44
Since it shifts by "32 - rotate amount", arm_analyze_prologue does a 32
bit shift of a 32 bit type, which is caught by UBSan.
Fix it by factoring out the decoding of immediates in a new function,
arm_expand_immediate.
I added a selftest for arm_analyze_prologue that replicates the
instruction sequence. Without the fix, it crashes GDB if it is build
with --enable-ubsan.
I initially wanted to re-use the abstract_memory_reader class already in
arm-tdep.c, used to make arm_process_record testable. However,
arm_process_record and arm_analyze_prologue don't use the same kind of
memory reading functions. arm_process_record uses a function that
returns an error status on failure while arm_analyze_prologue uses one
that throws an exception. Since i didn't want to introduce any other
behavior change, I decided to just introduce a separate interface
(arm_instruction_reader). It is derived from
abstract_instruction_reader in aarch64-tdep.c.
gdb/ChangeLog:
PR gdb/26835
* arm-tdep.c (class arm_instruction_reader): New.
(target_arm_instruction_reader): New.
(arm_analyze_prologue): Add instruction reader parameter and use
it. Use arm_expand_immediate.
(class target_arm_instruction_reader): Adjust.
(arm_skip_prologue): Adjust.
(arm_expand_immediate): New.
(arm_scan_prologue): Adjust.
(arm_analyze_prologue_test): New.
(class test_arm_instruction_reader): New.
Change-Id: Ieb1c1799bd66f8c7421384f44f5c2777b578ff8d
---
gdb/arm-tdep.c | 144 +++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 121 insertions(+), 23 deletions(-)
diff --git a/gdb/arm-tdep.c b/gdb/arm-tdep.c
index 82e8ec4df49c..7f47654233dd 100644
--- a/gdb/arm-tdep.c
+++ b/gdb/arm-tdep.c
@@ -285,10 +285,34 @@ struct arm_prologue_cache
struct trad_frame_saved_reg *saved_regs;
};
-static CORE_ADDR arm_analyze_prologue (struct gdbarch *gdbarch,
- CORE_ADDR prologue_start,
- CORE_ADDR prologue_end,
- struct arm_prologue_cache *cache);
+namespace {
+
+/* Abstract class to read ARM instructions from memory. */
+
+class arm_instruction_reader
+{
+public:
+ /* Read a 4 bytes instruction bytes from memory using the BYTE_ORDER
+ endianness. */
+ virtual uint32_t read (CORE_ADDR memaddr, bfd_endian byte_order) const = 0;
+};
+
+/* Read instructions from target memory. */
+
+class target_arm_instruction_reader : public arm_instruction_reader
+{
+public:
+ uint32_t read (CORE_ADDR memaddr, bfd_endian byte_order) const override
+ {
+ return read_code_unsigned_integer (memaddr, 4, byte_order);
+ }
+};
+
+} /* namespace */
+
+static CORE_ADDR arm_analyze_prologue
+ (struct gdbarch *gdbarch, CORE_ADDR prologue_start, CORE_ADDR prologue_end,
+ struct arm_prologue_cache *cache, const arm_instruction_reader &insn_reader);
/* Architecture version for displaced stepping. This effects the behaviour of
certain instructions, and really should not be hard-wired. */
@@ -1383,8 +1407,9 @@ arm_skip_prologue (struct gdbarch *gdbarch, CORE_ADDR pc)
analyzed_limit = thumb_analyze_prologue (gdbarch, func_addr,
post_prologue_pc, NULL);
else
- analyzed_limit = arm_analyze_prologue (gdbarch, func_addr,
- post_prologue_pc, NULL);
+ analyzed_limit
+ = arm_analyze_prologue (gdbarch, func_addr, post_prologue_pc,
+ NULL, target_arm_instruction_reader ());
if (analyzed_limit != post_prologue_pc)
return func_addr;
@@ -1409,7 +1434,8 @@ arm_skip_prologue (struct gdbarch *gdbarch, CORE_ADDR pc)
if (arm_pc_is_thumb (gdbarch, pc))
return thumb_analyze_prologue (gdbarch, pc, limit_pc, NULL);
else
- return arm_analyze_prologue (gdbarch, pc, limit_pc, NULL);
+ return arm_analyze_prologue (gdbarch, pc, limit_pc, NULL,
+ target_arm_instruction_reader ());
}
/* *INDENT-OFF* */
@@ -1485,6 +1511,26 @@ arm_instruction_restores_sp (unsigned int insn)
return 0;
}
+/* Implement immediate value decoding, as described in section A5.2.4
+ (Modified immediate constants in ARM instructions) of the ARM Architecture
+ Reference Manual. */
+
+static uint32_t
+arm_expand_immediate (uint32_t imm)
+{
+ /* Immediate values are 12 bits long. */
+ gdb_assert ((imm & 0xfffff000) == 0);
+
+ uint32_t unrotated_value = imm & 0xff;
+ uint32_t rotate_amount = (imm & 0xf00) >> 7;
+
+ if (rotate_amount == 0)
+ return unrotated_value;
+
+ return ((unrotated_value >> rotate_amount)
+ | (unrotated_value << (32 - rotate_amount)));
+}
+
/* Analyze an ARM mode prologue starting at PROLOGUE_START and
continuing no further than PROLOGUE_END. If CACHE is non-NULL,
fill it in. Return the first address not recognized as a prologue
@@ -1498,7 +1544,8 @@ arm_instruction_restores_sp (unsigned int insn)
static CORE_ADDR
arm_analyze_prologue (struct gdbarch *gdbarch,
CORE_ADDR prologue_start, CORE_ADDR prologue_end,
- struct arm_prologue_cache *cache)
+ struct arm_prologue_cache *cache,
+ const arm_instruction_reader &insn_reader)
{
enum bfd_endian byte_order_for_code = gdbarch_byte_order_for_code (gdbarch);
int regno;
@@ -1524,8 +1571,7 @@ arm_analyze_prologue (struct gdbarch *gdbarch,
current_pc < prologue_end;
current_pc += 4)
{
- unsigned int insn
- = read_code_unsigned_integer (current_pc, 4, byte_order_for_code);
+ uint32_t insn = insn_reader.read (current_pc, byte_order_for_code);
if (insn == 0xe1a0c00d) /* mov ip, sp */
{
@@ -1535,20 +1581,16 @@ arm_analyze_prologue (struct gdbarch *gdbarch,
else if ((insn & 0xfff00000) == 0xe2800000 /* add Rd, Rn, #n */
&& pv_is_register (regs[bits (insn, 16, 19)], ARM_SP_REGNUM))
{
- unsigned imm = insn & 0xff; /* immediate value */
- unsigned rot = (insn & 0xf00) >> 7; /* rotate amount */
+ uint32_t imm = arm_expand_immediate (insn & 0xfff);
int rd = bits (insn, 12, 15);
- imm = (imm >> rot) | (imm << (32 - rot));
regs[rd] = pv_add_constant (regs[bits (insn, 16, 19)], imm);
continue;
}
else if ((insn & 0xfff00000) == 0xe2400000 /* sub Rd, Rn, #n */
&& pv_is_register (regs[bits (insn, 16, 19)], ARM_SP_REGNUM))
{
- unsigned imm = insn & 0xff; /* immediate value */
- unsigned rot = (insn & 0xf00) >> 7; /* rotate amount */
+ uint32_t imm = arm_expand_immediate (insn & 0xfff);
int rd = bits (insn, 12, 15);
- imm = (imm >> rot) | (imm << (32 - rot));
regs[rd] = pv_add_constant (regs[bits (insn, 16, 19)], -imm);
continue;
}
@@ -1604,16 +1646,12 @@ arm_analyze_prologue (struct gdbarch *gdbarch,
}
else if ((insn & 0xfffff000) == 0xe24cb000) /* sub fp, ip #n */
{
- unsigned imm = insn & 0xff; /* immediate value */
- unsigned rot = (insn & 0xf00) >> 7; /* rotate amount */
- imm = (imm >> rot) | (imm << (32 - rot));
+ uint32_t imm = arm_expand_immediate (insn & 0xfff);
regs[ARM_FP_REGNUM] = pv_add_constant (regs[ARM_IP_REGNUM], -imm);
}
else if ((insn & 0xfffff000) == 0xe24dd000) /* sub sp, sp #n */
{
- unsigned imm = insn & 0xff; /* immediate value */
- unsigned rot = (insn & 0xf00) >> 7; /* rotate amount */
- imm = (imm >> rot) | (imm << (32 - rot));
+ uint32_t imm = arm_expand_immediate(insn & 0xfff);
regs[ARM_SP_REGNUM] = pv_add_constant (regs[ARM_SP_REGNUM], -imm);
}
else if ((insn & 0xffff7fff) == 0xed6d0103 /* stfe f?,
@@ -1841,7 +1879,8 @@ arm_scan_prologue (struct frame_info *this_frame,
if (prev_pc < prologue_end)
prologue_end = prev_pc;
- arm_analyze_prologue (gdbarch, prologue_start, prologue_end, cache);
+ arm_analyze_prologue (gdbarch, prologue_start, prologue_end, cache,
+ target_arm_instruction_reader ());
}
static struct arm_prologue_cache *
@@ -9492,6 +9531,7 @@ arm_dump_tdep (struct gdbarch *gdbarch, struct ui_file *file)
namespace selftests
{
static void arm_record_test (void);
+static void arm_analyze_prologue_test ();
}
#endif
@@ -9618,6 +9658,7 @@ vfp - VFP co-processor."),
#if GDB_SELF_TEST
selftests::register_test ("arm-record", selftests::arm_record_test);
+ selftests::register_test ("arm_analyze_prologue", selftests::arm_analyze_prologue_test);
#endif
}
@@ -13254,6 +13295,63 @@ arm_record_test (void)
SELF_CHECK (arm_record.arm_regs[0] == 7);
}
}
+
+/* Instruction reader from manually cooked instruction sequences. */
+
+class test_arm_instruction_reader : public arm_instruction_reader
+{
+public:
+ template<size_t SIZE>
+ explicit test_arm_instruction_reader (const uint32_t (&insns)[SIZE])
+ : m_insns (insns), m_insns_size (SIZE)
+ {}
+
+ uint32_t read (CORE_ADDR memaddr, enum bfd_endian byte_order) const override
+ {
+ SELF_CHECK (memaddr % 4 == 0);
+ SELF_CHECK (memaddr / 4 < m_insns_size);
+
+ return m_insns[memaddr / 4];
+ }
+
+private:
+ const uint32_t *m_insns;
+ size_t m_insns_size;
+};
+
+static void
+arm_analyze_prologue_test ()
+{
+ for (bfd_endian endianness : {BFD_ENDIAN_LITTLE, BFD_ENDIAN_BIG})
+ {
+ struct gdbarch_info info;
+ gdbarch_info_init (&info);
+ info.byte_order = endianness;
+ info.byte_order_for_code = endianness;
+ info.bfd_arch_info = bfd_scan_arch ("arm");
+
+ struct gdbarch *gdbarch = gdbarch_find_by_info (info);
+
+ SELF_CHECK (gdbarch != NULL);
+
+ /* The "sub" instruction contains an immediate value rotate count of 0,
+ which resulted in a 32-bit shift of a 32-bit value, caught by
+ UBSan. */
+ const uint32_t insns[] = {
+ 0xe92d4ff0, /* push {r4, r5, r6, r7, r8, r9, sl, fp, lr} */
+ 0xe1a05000, /* mov r5, r0 */
+ 0xe5903020, /* ldr r3, [r0, #32] */
+ 0xe24dd044, /* sub sp, sp, #68 ; 0x44 */
+ };
+
+ test_arm_instruction_reader mem_reader (insns);
+ arm_prologue_cache cache;
+ cache.saved_regs = trad_frame_alloc_saved_regs (gdbarch);
+
+ arm_analyze_prologue (gdbarch, 0, sizeof (insns) - 1, &cache, mem_reader);
+ }
+}
+
} // namespace selftests
#endif /* GDB_SELF_TEST */
--
2.26.2
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH] gdb/arm: avoid undefined behavior shift when decoding immediate value
2020-11-09 20:49 [PATCH] gdb/arm: avoid undefined behavior shift when decoding immediate value Simon Marchi
@ 2020-11-11 10:00 ` Alan Hayward
2020-11-11 20:44 ` Simon Marchi
2020-11-13 22:35 ` Luis Machado
1 sibling, 1 reply; 6+ messages in thread
From: Alan Hayward @ 2020-11-11 10:00 UTC (permalink / raw)
To: Simon Marchi; +Cc: gdb-patches\@sourceware.org, nd
> On 9 Nov 2020, at 20:49, Simon Marchi via Gdb-patches <gdb-patches@sourceware.org> wrote:
>
> From: Simon Marchi <simon.marchi@polymtl.ca>
>
> When loading the code file provided in PR 26828 and GDB is build with
> UBSan, we get:
>
> Core was generated by `./Foo'.
> Program terminated with signal SIGABRT, Aborted.
> #0 0xb6c3809c in pthread_cond_wait () from /home/simark/build/binutils-gdb/gdb/repo/lib/libpthread.so.0
> [Current thread is 1 (LWP 29367)]
> (gdb) bt
> /home/simark/src/binutils-gdb/gdb/arm-tdep.c:1551:30: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
>
This error is because the code is technically wrong - but in reality
compilers will plant what we really wanted?
> The sequence of instructions at pthread_cond_wait, in the
> libpthread.so.0 library, contains this instruction with an immediate
> constant with a "rotate amount" of 0:
>
> e24dd044 sub sp, sp, #68 ; 0x44
>
> Since it shifts by "32 - rotate amount", arm_analyze_prologue does a 32
> bit shift of a 32 bit type, which is caught by UBSan.
>
Minor nit - it took me a while to figure out that the “it” in “Since it”
is the following arm_analyze_prologue and not something in the previous
sentence.
> Fix it by factoring out the decoding of immediates in a new function,
> arm_expand_immediate.
>
> I added a selftest for arm_analyze_prologue that replicates the
> instruction sequence. Without the fix, it crashes GDB if it is build
> with --enable-ubsan.
>
> I initially wanted to re-use the abstract_memory_reader class already in
> arm-tdep.c, used to make arm_process_record testable. However,
> arm_process_record and arm_analyze_prologue don't use the same kind of
> memory reading functions. arm_process_record uses a function that
> returns an error status on failure while arm_analyze_prologue uses one
> that throws an exception. Since i didn't want to introduce any other
> behavior change, I decided to just introduce a separate interface
> (arm_instruction_reader). It is derived from
> abstract_instruction_reader in aarch64-tdep.c.
I both don’t like this and can’t see a better way of doing it :)
So, I’m ok with it
>
> gdb/ChangeLog:
>
> PR gdb/26835
> * arm-tdep.c (class arm_instruction_reader): New.
> (target_arm_instruction_reader): New.
> (arm_analyze_prologue): Add instruction reader parameter and use
> it. Use arm_expand_immediate.
> (class target_arm_instruction_reader): Adjust.
> (arm_skip_prologue): Adjust.
> (arm_expand_immediate): New.
> (arm_scan_prologue): Adjust.
> (arm_analyze_prologue_test): New.
> (class test_arm_instruction_reader): New.
>
> Change-Id: Ieb1c1799bd66f8c7421384f44f5c2777b578ff8d
> ---
> gdb/arm-tdep.c | 144 +++++++++++++++++++++++++++++++++++++++++--------
> 1 file changed, 121 insertions(+), 23 deletions(-)
>
> diff --git a/gdb/arm-tdep.c b/gdb/arm-tdep.c
> index 82e8ec4df49c..7f47654233dd 100644
> --- a/gdb/arm-tdep.c
> +++ b/gdb/arm-tdep.c
> @@ -285,10 +285,34 @@ struct arm_prologue_cache
> struct trad_frame_saved_reg *saved_regs;
> };
>
> -static CORE_ADDR arm_analyze_prologue (struct gdbarch *gdbarch,
> - CORE_ADDR prologue_start,
> - CORE_ADDR prologue_end,
> - struct arm_prologue_cache *cache);
> +namespace {
> +
> +/* Abstract class to read ARM instructions from memory. */
> +
> +class arm_instruction_reader
> +{
> +public:
> + /* Read a 4 bytes instruction bytes from memory using the BYTE_ORDER
> + endianness. */
> + virtual uint32_t read (CORE_ADDR memaddr, bfd_endian byte_order) const = 0;
> +};
> +
> +/* Read instructions from target memory. */
> +
> +class target_arm_instruction_reader : public arm_instruction_reader
> +{
> +public:
> + uint32_t read (CORE_ADDR memaddr, bfd_endian byte_order) const override
> + {
> + return read_code_unsigned_integer (memaddr, 4, byte_order);
> + }
> +};
> +
> +} /* namespace */
> +
> +static CORE_ADDR arm_analyze_prologue
> + (struct gdbarch *gdbarch, CORE_ADDR prologue_start, CORE_ADDR prologue_end,
> + struct arm_prologue_cache *cache, const arm_instruction_reader &insn_reader);
>
> /* Architecture version for displaced stepping. This effects the behaviour of
> certain instructions, and really should not be hard-wired. */
> @@ -1383,8 +1407,9 @@ arm_skip_prologue (struct gdbarch *gdbarch, CORE_ADDR pc)
> analyzed_limit = thumb_analyze_prologue (gdbarch, func_addr,
> post_prologue_pc, NULL);
> else
> - analyzed_limit = arm_analyze_prologue (gdbarch, func_addr,
> - post_prologue_pc, NULL);
> + analyzed_limit
> + = arm_analyze_prologue (gdbarch, func_addr, post_prologue_pc,
> + NULL, target_arm_instruction_reader ());
>
> if (analyzed_limit != post_prologue_pc)
> return func_addr;
> @@ -1409,7 +1434,8 @@ arm_skip_prologue (struct gdbarch *gdbarch, CORE_ADDR pc)
> if (arm_pc_is_thumb (gdbarch, pc))
> return thumb_analyze_prologue (gdbarch, pc, limit_pc, NULL);
> else
> - return arm_analyze_prologue (gdbarch, pc, limit_pc, NULL);
> + return arm_analyze_prologue (gdbarch, pc, limit_pc, NULL,
> + target_arm_instruction_reader ());
> }
>
> /* *INDENT-OFF* */
> @@ -1485,6 +1511,26 @@ arm_instruction_restores_sp (unsigned int insn)
> return 0;
> }
>
> +/* Implement immediate value decoding, as described in section A5.2.4
> + (Modified immediate constants in ARM instructions) of the ARM Architecture
> + Reference Manual. */
"of the ARM Architecture Reference Manual (ARMv7-A and ARMv7-R edition)"
Just to make sure no one is looking for that section in the v8 doc.
> +
> +static uint32_t
> +arm_expand_immediate (uint32_t imm)
> +{
> + /* Immediate values are 12 bits long. */
> + gdb_assert ((imm & 0xfffff000) == 0);
> +
> + uint32_t unrotated_value = imm & 0xff;
> + uint32_t rotate_amount = (imm & 0xf00) >> 7;
> +
> + if (rotate_amount == 0)
> + return unrotated_value;
> +
> + return ((unrotated_value >> rotate_amount)
> + | (unrotated_value << (32 - rotate_amount)));
> +}
> +
> /* Analyze an ARM mode prologue starting at PROLOGUE_START and
> continuing no further than PROLOGUE_END. If CACHE is non-NULL,
> fill it in. Return the first address not recognized as a prologue
> @@ -1498,7 +1544,8 @@ arm_instruction_restores_sp (unsigned int insn)
> static CORE_ADDR
> arm_analyze_prologue (struct gdbarch *gdbarch,
> CORE_ADDR prologue_start, CORE_ADDR prologue_end,
> - struct arm_prologue_cache *cache)
> + struct arm_prologue_cache *cache,
> + const arm_instruction_reader &insn_reader)
> {
> enum bfd_endian byte_order_for_code = gdbarch_byte_order_for_code (gdbarch);
> int regno;
> @@ -1524,8 +1571,7 @@ arm_analyze_prologue (struct gdbarch *gdbarch,
> current_pc < prologue_end;
> current_pc += 4)
> {
> - unsigned int insn
> - = read_code_unsigned_integer (current_pc, 4, byte_order_for_code);
> + uint32_t insn = insn_reader.read (current_pc, byte_order_for_code);
>
> if (insn == 0xe1a0c00d) /* mov ip, sp */
> {
> @@ -1535,20 +1581,16 @@ arm_analyze_prologue (struct gdbarch *gdbarch,
> else if ((insn & 0xfff00000) == 0xe2800000 /* add Rd, Rn, #n */
> && pv_is_register (regs[bits (insn, 16, 19)], ARM_SP_REGNUM))
> {
> - unsigned imm = insn & 0xff; /* immediate value */
> - unsigned rot = (insn & 0xf00) >> 7; /* rotate amount */
> + uint32_t imm = arm_expand_immediate (insn & 0xfff);
> int rd = bits (insn, 12, 15);
> - imm = (imm >> rot) | (imm << (32 - rot));
> regs[rd] = pv_add_constant (regs[bits (insn, 16, 19)], imm);
> continue;
> }
> else if ((insn & 0xfff00000) == 0xe2400000 /* sub Rd, Rn, #n */
> && pv_is_register (regs[bits (insn, 16, 19)], ARM_SP_REGNUM))
> {
> - unsigned imm = insn & 0xff; /* immediate value */
> - unsigned rot = (insn & 0xf00) >> 7; /* rotate amount */
> + uint32_t imm = arm_expand_immediate (insn & 0xfff);
> int rd = bits (insn, 12, 15);
> - imm = (imm >> rot) | (imm << (32 - rot));
> regs[rd] = pv_add_constant (regs[bits (insn, 16, 19)], -imm);
> continue;
> }
> @@ -1604,16 +1646,12 @@ arm_analyze_prologue (struct gdbarch *gdbarch,
> }
> else if ((insn & 0xfffff000) == 0xe24cb000) /* sub fp, ip #n */
> {
> - unsigned imm = insn & 0xff; /* immediate value */
> - unsigned rot = (insn & 0xf00) >> 7; /* rotate amount */
> - imm = (imm >> rot) | (imm << (32 - rot));
> + uint32_t imm = arm_expand_immediate (insn & 0xfff);
> regs[ARM_FP_REGNUM] = pv_add_constant (regs[ARM_IP_REGNUM], -imm);
> }
> else if ((insn & 0xfffff000) == 0xe24dd000) /* sub sp, sp #n */
> {
> - unsigned imm = insn & 0xff; /* immediate value */
> - unsigned rot = (insn & 0xf00) >> 7; /* rotate amount */
> - imm = (imm >> rot) | (imm << (32 - rot));
> + uint32_t imm = arm_expand_immediate(insn & 0xfff);
> regs[ARM_SP_REGNUM] = pv_add_constant (regs[ARM_SP_REGNUM], -imm);
> }
> else if ((insn & 0xffff7fff) == 0xed6d0103 /* stfe f?,
> @@ -1841,7 +1879,8 @@ arm_scan_prologue (struct frame_info *this_frame,
> if (prev_pc < prologue_end)
> prologue_end = prev_pc;
>
> - arm_analyze_prologue (gdbarch, prologue_start, prologue_end, cache);
> + arm_analyze_prologue (gdbarch, prologue_start, prologue_end, cache,
> + target_arm_instruction_reader ());
> }
>
> static struct arm_prologue_cache *
> @@ -9492,6 +9531,7 @@ arm_dump_tdep (struct gdbarch *gdbarch, struct ui_file *file)
> namespace selftests
> {
> static void arm_record_test (void);
> +static void arm_analyze_prologue_test ();
> }
> #endif
>
> @@ -9618,6 +9658,7 @@ vfp - VFP co-processor."),
>
> #if GDB_SELF_TEST
> selftests::register_test ("arm-record", selftests::arm_record_test);
> + selftests::register_test ("arm_analyze_prologue", selftests::arm_analyze_prologue_test);
> #endif
Given the size of the tdep files, I wonder if it’s worth putting the tests into separate
files, eg arm-tdep-selftests.c
(Probably not for this patch though)
>
> }
> @@ -13254,6 +13295,63 @@ arm_record_test (void)
> SELF_CHECK (arm_record.arm_regs[0] == 7);
> }
> }
> +
> +/* Instruction reader from manually cooked instruction sequences. */
> +
> +class test_arm_instruction_reader : public arm_instruction_reader
> +{
> +public:
> + template<size_t SIZE>
> + explicit test_arm_instruction_reader (const uint32_t (&insns)[SIZE])
> + : m_insns (insns), m_insns_size (SIZE)
Why the need for a template?
Is it just so that m_insns_size can be determined automatically?
> + {}
> +
> + uint32_t read (CORE_ADDR memaddr, enum bfd_endian byte_order) const override
> + {
> + SELF_CHECK (memaddr % 4 == 0);
> + SELF_CHECK (memaddr / 4 < m_insns_size);
> +
> + return m_insns[memaddr / 4];
> + }
> +
> +private:
> + const uint32_t *m_insns;
> + size_t m_insns_size;
> +};
> +
> +static void
> +arm_analyze_prologue_test ()
> +{
> + for (bfd_endian endianness : {BFD_ENDIAN_LITTLE, BFD_ENDIAN_BIG})
> + {
> + struct gdbarch_info info;
> + gdbarch_info_init (&info);
> + info.byte_order = endianness;
> + info.byte_order_for_code = endianness;
> + info.bfd_arch_info = bfd_scan_arch ("arm");
> +
> + struct gdbarch *gdbarch = gdbarch_find_by_info (info);
> +
> + SELF_CHECK (gdbarch != NULL);
> +
> + /* The "sub" instruction contains an immediate value rotate count of 0,
> + which resulted in a 32-bit shift of a 32-bit value, caught by
> + UBSan. */
> + const uint32_t insns[] = {
> + 0xe92d4ff0, /* push {r4, r5, r6, r7, r8, r9, sl, fp, lr} */
> + 0xe1a05000, /* mov r5, r0 */
> + 0xe5903020, /* ldr r3, [r0, #32] */
> + 0xe24dd044, /* sub sp, sp, #68 ; 0x44 */
> + };
> +
> + test_arm_instruction_reader mem_reader (insns);
> + arm_prologue_cache cache;
> + cache.saved_regs = trad_frame_alloc_saved_regs (gdbarch);
> +
> + arm_analyze_prologue (gdbarch, 0, sizeof (insns) - 1, &cache, mem_reader);
> + }
> +}
> +
> } // namespace selftests
> #endif /* GDB_SELF_TEST */
>
> --
> 2.26.2
>
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH] gdb/arm: avoid undefined behavior shift when decoding immediate value
2020-11-11 10:00 ` Alan Hayward
@ 2020-11-11 20:44 ` Simon Marchi
2020-11-13 17:05 ` Simon Marchi
0 siblings, 1 reply; 6+ messages in thread
From: Simon Marchi @ 2020-11-11 20:44 UTC (permalink / raw)
To: Alan Hayward; +Cc: gdb-patches\@sourceware.org, nd
On 2020-11-11 5:00 a.m., Alan Hayward wrote:
>
>
>> On 9 Nov 2020, at 20:49, Simon Marchi via Gdb-patches <gdb-patches@sourceware.org> wrote:
>>
>> From: Simon Marchi <simon.marchi@polymtl.ca>
>>
>> When loading the code file provided in PR 26828 and GDB is build with
>> UBSan, we get:
>>
>> Core was generated by `./Foo'.
>> Program terminated with signal SIGABRT, Aborted.
>> #0 0xb6c3809c in pthread_cond_wait () from /home/simark/build/binutils-gdb/gdb/repo/lib/libpthread.so.0
>> [Current thread is 1 (LWP 29367)]
>> (gdb) bt
>> /home/simark/src/binutils-gdb/gdb/arm-tdep.c:1551:30: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
>>
>
> This error is because the code is technically wrong - but in reality
> compilers will plant what we really wanted?
Well, we don't really know, different things can happen on different
systems. I did this program for fun:
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char **argv) {
unsigned int foo = 0xabababab;
unsigned int shift = atoi(argv[1]);
printf("Shifting by %d\n", shift);
foo = foo >> shift;
printf("%x\n", foo);
return 0;
}
Running `./a.out 32`, I get this on AMD64:
Shifting by 32
abababab
And I get this on PowerPC and ARM:
Shifting by 32
0
>> The sequence of instructions at pthread_cond_wait, in the
>> libpthread.so.0 library, contains this instruction with an immediate
>> constant with a "rotate amount" of 0:
>>
>> e24dd044 sub sp, sp, #68 ; 0x44
>>
>> Since it shifts by "32 - rotate amount", arm_analyze_prologue does a 32
>> bit shift of a 32 bit type, which is caught by UBSan.
>>
>
> Minor nit - it took me a while to figure out that the “it” in “Since it”
> is the following arm_analyze_prologue and not something in the previous
> sentence.
Good point, I swapped "it" and "arm_analyze_prologue" to make sure
"arm_analyze_prologue" comes before.
>
>> Fix it by factoring out the decoding of immediates in a new function,
>> arm_expand_immediate.
>>
>> I added a selftest for arm_analyze_prologue that replicates the
>> instruction sequence. Without the fix, it crashes GDB if it is build
>> with --enable-ubsan.
>>
>> I initially wanted to re-use the abstract_memory_reader class already in
>> arm-tdep.c, used to make arm_process_record testable. However,
>> arm_process_record and arm_analyze_prologue don't use the same kind of
>> memory reading functions. arm_process_record uses a function that
>> returns an error status on failure while arm_analyze_prologue uses one
>> that throws an exception. Since i didn't want to introduce any other
>> behavior change, I decided to just introduce a separate interface
>> (arm_instruction_reader). It is derived from
>> abstract_instruction_reader in aarch64-tdep.c.
>
> I both don’t like this and can’t see a better way of doing it :)
> So, I’m ok with it
Same. I really wanted not to add another class, but I also didn't want
this simple fix to become a huge refactor.
>> @@ -1485,6 +1511,26 @@ arm_instruction_restores_sp (unsigned int insn)
>> return 0;
>> }
>>
>> +/* Implement immediate value decoding, as described in section A5.2.4
>> + (Modified immediate constants in ARM instructions) of the ARM Architecture
>> + Reference Manual. */
>
>
> "of the ARM Architecture Reference Manual (ARMv7-A and ARMv7-R edition)"
>
> Just to make sure no one is looking for that section in the v8 doc.
Done.
>> @@ -9618,6 +9658,7 @@ vfp - VFP co-processor."),
>>
>> #if GDB_SELF_TEST
>> selftests::register_test ("arm-record", selftests::arm_record_test);
>> + selftests::register_test ("arm_analyze_prologue", selftests::arm_analyze_prologue_test);
>> #endif
>
> Given the size of the tdep files, I wonder if it’s worth putting the tests into separate
> files, eg arm-tdep-selftests.c
> (Probably not for this patch though)
Possibly. That requires exposing (making non-static) functions that are not usually exposed
though.
>> }
>> @@ -13254,6 +13295,63 @@ arm_record_test (void)
>> SELF_CHECK (arm_record.arm_regs[0] == 7);
>> }
>> }
>> +
>> +/* Instruction reader from manually cooked instruction sequences. */
>> +
>> +class test_arm_instruction_reader : public arm_instruction_reader
>> +{
>> +public:
>> + template<size_t SIZE>
>> + explicit test_arm_instruction_reader (const uint32_t (&insns)[SIZE])
>> + : m_insns (insns), m_insns_size (SIZE)
>
> Why the need for a template?
> Is it just so that m_insns_size can be determined automatically?
I don't really know, I just copied the AArch64 one :). But yeah I think
that's it. It could also use a gdb::array_view, that would be simpler I
think. I would change it for:
/* Instruction reader from manually cooked instruction sequences. */
class test_arm_instruction_reader : public arm_instruction_reader
{
public:
explicit test_arm_instruction_reader (gdb::array_view<const uint32_t> insns)
: m_insns (insns)
{}
uint32_t read (CORE_ADDR memaddr, enum bfd_endian byte_order) const override
{
SELF_CHECK (memaddr % 4 == 0);
SELF_CHECK (memaddr / 4 < m_insns.size ());
return m_insns[memaddr / 4];
}
private:
const gdb::array_view<const uint32_t> m_insns;
};
Simon
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH] gdb/arm: avoid undefined behavior shift when decoding immediate value
2020-11-11 20:44 ` Simon Marchi
@ 2020-11-13 17:05 ` Simon Marchi
0 siblings, 0 replies; 6+ messages in thread
From: Simon Marchi @ 2020-11-13 17:05 UTC (permalink / raw)
To: Simon Marchi, Alan Hayward; +Cc: nd, gdb-patches\@sourceware.org
On 2020-11-11 3:44 p.m., Simon Marchi via Gdb-patches wrote:
> On 2020-11-11 5:00 a.m., Alan Hayward wrote:
>>
>>
>>> On 9 Nov 2020, at 20:49, Simon Marchi via Gdb-patches <gdb-patches@sourceware.org> wrote:
>>>
>>> From: Simon Marchi <simon.marchi@polymtl.ca>
>>>
>>> When loading the code file provided in PR 26828 and GDB is build with
>>> UBSan, we get:
>>>
>>> Core was generated by `./Foo'.
>>> Program terminated with signal SIGABRT, Aborted.
>>> #0 0xb6c3809c in pthread_cond_wait () from /home/simark/build/binutils-gdb/gdb/repo/lib/libpthread.so.0
>>> [Current thread is 1 (LWP 29367)]
>>> (gdb) bt
>>> /home/simark/src/binutils-gdb/gdb/arm-tdep.c:1551:30: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
>>>
>>
>> This error is because the code is technically wrong - but in reality
>> compilers will plant what we really wanted?
>
> Well, we don't really know, different things can happen on different
> systems. I did this program for fun:
>
> #include <stdlib.h>
> #include <stdio.h>
>
> int main(int argc, char **argv) {
> unsigned int foo = 0xabababab;
> unsigned int shift = atoi(argv[1]);
> printf("Shifting by %d\n", shift);
> foo = foo >> shift;
> printf("%x\n", foo);
> return 0;
> }
>
> Running `./a.out 32`, I get this on AMD64:
>
> Shifting by 32
> abababab
>
> And I get this on PowerPC and ARM:
>
> Shifting by 32
> 0
>
>>> The sequence of instructions at pthread_cond_wait, in the
>>> libpthread.so.0 library, contains this instruction with an immediate
>>> constant with a "rotate amount" of 0:
>>>
>>> e24dd044 sub sp, sp, #68 ; 0x44
>>>
>>> Since it shifts by "32 - rotate amount", arm_analyze_prologue does a 32
>>> bit shift of a 32 bit type, which is caught by UBSan.
>>>
>>
>> Minor nit - it took me a while to figure out that the “it” in “Since it”
>> is the following arm_analyze_prologue and not something in the previous
>> sentence.
>
> Good point, I swapped "it" and "arm_analyze_prologue" to make sure
> "arm_analyze_prologue" comes before.
>
>>
>>> Fix it by factoring out the decoding of immediates in a new function,
>>> arm_expand_immediate.
>>>
>>> I added a selftest for arm_analyze_prologue that replicates the
>>> instruction sequence. Without the fix, it crashes GDB if it is build
>>> with --enable-ubsan.
>>>
>>> I initially wanted to re-use the abstract_memory_reader class already in
>>> arm-tdep.c, used to make arm_process_record testable. However,
>>> arm_process_record and arm_analyze_prologue don't use the same kind of
>>> memory reading functions. arm_process_record uses a function that
>>> returns an error status on failure while arm_analyze_prologue uses one
>>> that throws an exception. Since i didn't want to introduce any other
>>> behavior change, I decided to just introduce a separate interface
>>> (arm_instruction_reader). It is derived from
>>> abstract_instruction_reader in aarch64-tdep.c.
>>
>> I both don’t like this and can’t see a better way of doing it :)
>> So, I’m ok with it
>
> Same. I really wanted not to add another class, but I also didn't want
> this simple fix to become a huge refactor.
>
>>> @@ -1485,6 +1511,26 @@ arm_instruction_restores_sp (unsigned int insn)
>>> return 0;
>>> }
>>>
>>> +/* Implement immediate value decoding, as described in section A5.2.4
>>> + (Modified immediate constants in ARM instructions) of the ARM Architecture
>>> + Reference Manual. */
>>
>>
>> "of the ARM Architecture Reference Manual (ARMv7-A and ARMv7-R edition)"
>>
>> Just to make sure no one is looking for that section in the v8 doc.
>
> Done.
>
>>> @@ -9618,6 +9658,7 @@ vfp - VFP co-processor."),
>>>
>>> #if GDB_SELF_TEST
>>> selftests::register_test ("arm-record", selftests::arm_record_test);
>>> + selftests::register_test ("arm_analyze_prologue", selftests::arm_analyze_prologue_test);
>>> #endif
>>
>> Given the size of the tdep files, I wonder if it’s worth putting the tests into separate
>> files, eg arm-tdep-selftests.c
>> (Probably not for this patch though)
>
> Possibly. That requires exposing (making non-static) functions that are not usually exposed
> though.
>
>>> }
>>> @@ -13254,6 +13295,63 @@ arm_record_test (void)
>>> SELF_CHECK (arm_record.arm_regs[0] == 7);
>>> }
>>> }
>>> +
>>> +/* Instruction reader from manually cooked instruction sequences. */
>>> +
>>> +class test_arm_instruction_reader : public arm_instruction_reader
>>> +{
>>> +public:
>>> + template<size_t SIZE>
>>> + explicit test_arm_instruction_reader (const uint32_t (&insns)[SIZE])
>>> + : m_insns (insns), m_insns_size (SIZE)
>>
>> Why the need for a template?
>> Is it just so that m_insns_size can be determined automatically?
>
> I don't really know, I just copied the AArch64 one :). But yeah I think
> that's it. It could also use a gdb::array_view, that would be simpler I
> think. I would change it for:
>
> /* Instruction reader from manually cooked instruction sequences. */
>
> class test_arm_instruction_reader : public arm_instruction_reader
> {
> public:
> explicit test_arm_instruction_reader (gdb::array_view<const uint32_t> insns)
> : m_insns (insns)
> {}
>
> uint32_t read (CORE_ADDR memaddr, enum bfd_endian byte_order) const override
> {
> SELF_CHECK (memaddr % 4 == 0);
> SELF_CHECK (memaddr / 4 < m_insns.size ());
>
> return m_insns[memaddr / 4];
> }
>
> private:
> const gdb::array_view<const uint32_t> m_insns;
> };
>
> Simon
>
I pushed the patch with the changes mentioned above.
Simon
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH] gdb/arm: avoid undefined behavior shift when decoding immediate value
2020-11-09 20:49 [PATCH] gdb/arm: avoid undefined behavior shift when decoding immediate value Simon Marchi
2020-11-11 10:00 ` Alan Hayward
@ 2020-11-13 22:35 ` Luis Machado
2020-11-14 16:20 ` Simon Marchi
1 sibling, 1 reply; 6+ messages in thread
From: Luis Machado @ 2020-11-13 22:35 UTC (permalink / raw)
To: Simon Marchi, gdb-patches
On 11/9/20 5:49 PM, Simon Marchi via Gdb-patches wrote:
> From: Simon Marchi <simon.marchi@polymtl.ca>
>
> When loading the code file provided in PR 26828 and GDB is build with
> UBSan, we get:
>
> Core was generated by `./Foo'.
> Program terminated with signal SIGABRT, Aborted.
> #0 0xb6c3809c in pthread_cond_wait () from /home/simark/build/binutils-gdb/gdb/repo/lib/libpthread.so.0
> [Current thread is 1 (LWP 29367)]
> (gdb) bt
> /home/simark/src/binutils-gdb/gdb/arm-tdep.c:1551:30: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
>
> The sequence of instructions at pthread_cond_wait, in the
> libpthread.so.0 library, contains this instruction with an immediate
> constant with a "rotate amount" of 0:
>
> e24dd044 sub sp, sp, #68 ; 0x44
>
> Since it shifts by "32 - rotate amount", arm_analyze_prologue does a 32
> bit shift of a 32 bit type, which is caught by UBSan.
>
> Fix it by factoring out the decoding of immediates in a new function,
> arm_expand_immediate.
>
> I added a selftest for arm_analyze_prologue that replicates the
> instruction sequence. Without the fix, it crashes GDB if it is build
> with --enable-ubsan.
>
> I initially wanted to re-use the abstract_memory_reader class already in
> arm-tdep.c, used to make arm_process_record testable. However,
> arm_process_record and arm_analyze_prologue don't use the same kind of
> memory reading functions. arm_process_record uses a function that
> returns an error status on failure while arm_analyze_prologue uses one
> that throws an exception. Since i didn't want to introduce any other
> behavior change, I decided to just introduce a separate interface
> (arm_instruction_reader). It is derived from
> abstract_instruction_reader in aarch64-tdep.c.
>
> gdb/ChangeLog:
>
> PR gdb/26835
> * arm-tdep.c (class arm_instruction_reader): New.
> (target_arm_instruction_reader): New.
> (arm_analyze_prologue): Add instruction reader parameter and use
> it. Use arm_expand_immediate.
> (class target_arm_instruction_reader): Adjust.
> (arm_skip_prologue): Adjust.
> (arm_expand_immediate): New.
> (arm_scan_prologue): Adjust.
> (arm_analyze_prologue_test): New.
> (class test_arm_instruction_reader): New.
>
> Change-Id: Ieb1c1799bd66f8c7421384f44f5c2777b578ff8d
> ---
> gdb/arm-tdep.c | 144 +++++++++++++++++++++++++++++++++++++++++--------
> 1 file changed, 121 insertions(+), 23 deletions(-)
>
> diff --git a/gdb/arm-tdep.c b/gdb/arm-tdep.c
> index 82e8ec4df49c..7f47654233dd 100644
> --- a/gdb/arm-tdep.c
> +++ b/gdb/arm-tdep.c
> @@ -285,10 +285,34 @@ struct arm_prologue_cache
> struct trad_frame_saved_reg *saved_regs;
> };
>
> -static CORE_ADDR arm_analyze_prologue (struct gdbarch *gdbarch,
> - CORE_ADDR prologue_start,
> - CORE_ADDR prologue_end,
> - struct arm_prologue_cache *cache);
> +namespace {
> +
> +/* Abstract class to read ARM instructions from memory. */
> +
> +class arm_instruction_reader
> +{
> +public:
> + /* Read a 4 bytes instruction bytes from memory using the BYTE_ORDER
The comment above reads a bit funny.
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH] gdb/arm: avoid undefined behavior shift when decoding immediate value
2020-11-13 22:35 ` Luis Machado
@ 2020-11-14 16:20 ` Simon Marchi
0 siblings, 0 replies; 6+ messages in thread
From: Simon Marchi @ 2020-11-14 16:20 UTC (permalink / raw)
To: Luis Machado, Simon Marchi, gdb-patches
On 2020-11-13 5:35 p.m., Luis Machado via Gdb-patches wrote:
>> +public:
>> + /* Read a 4 bytes instruction bytes from memory using the BYTE_ORDER
> The comment above reads a bit funny.
Oh, thanks for spotting that, I fixed it.
Simon
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2020-11-14 16:20 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-11-09 20:49 [PATCH] gdb/arm: avoid undefined behavior shift when decoding immediate value Simon Marchi
2020-11-11 10:00 ` Alan Hayward
2020-11-11 20:44 ` Simon Marchi
2020-11-13 17:05 ` Simon Marchi
2020-11-13 22:35 ` Luis Machado
2020-11-14 16:20 ` Simon Marchi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).