| /* Copyright (C) 1986-2024 Free Software Foundation, Inc. |
| |
| This file is part of GDB. |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/>. */ |
| |
| #include "extract-store-integer.h" |
| #include "gdbtypes.h" |
| #include "gdbarch.h" |
| #include "gdbsupport/selftest.h" |
| |
| template<typename T, typename> |
| T |
| extract_integer (gdb::array_view<const gdb_byte> buf, enum bfd_endian byte_order) |
| { |
| typename std::make_unsigned<T>::type retval = 0; |
| |
| /* It is ok if BUF is wider than T, but only if the value is |
| representable. */ |
| bool bad_repr = false; |
| if (buf.size () > (int) sizeof (T)) |
| { |
| const size_t end = buf.size () - sizeof (T); |
| if (byte_order == BFD_ENDIAN_BIG) |
| { |
| for (size_t i = 0; i < end; ++i) |
| { |
| /* High bytes == 0 are always ok, and high bytes == 0xff |
| are ok when the type is signed. */ |
| if ((buf[i] == 0 |
| || (std::is_signed<T>::value && buf[i] == 0xff)) |
| /* All the high bytes must be the same, no |
| alternating 0 and 0xff. */ |
| && (i == 0 || buf[i - 1] == buf[i])) |
| { |
| /* Ok. */ |
| } |
| else |
| { |
| bad_repr = true; |
| break; |
| } |
| } |
| buf = buf.slice (end); |
| } |
| else |
| { |
| size_t bufsz = buf.size () - 1; |
| for (size_t i = bufsz; i >= end; --i) |
| { |
| /* High bytes == 0 are always ok, and high bytes == 0xff |
| are ok when the type is signed. */ |
| if ((buf[i] == 0 |
| || (std::is_signed<T>::value && buf[i] == 0xff)) |
| /* All the high bytes must be the same, no |
| alternating 0 and 0xff. */ |
| && (i == bufsz || buf[i] == buf[i + 1])) |
| { |
| /* Ok. */ |
| } |
| else |
| { |
| bad_repr = true; |
| break; |
| } |
| } |
| buf = buf.slice (0, end); |
| } |
| } |
| |
| if (bad_repr) |
| error (_("Value cannot be represented as integer of %d bytes."), |
| (int) sizeof (T)); |
| |
| /* Start at the most significant end of the integer, and work towards |
| the least significant. */ |
| if (byte_order == BFD_ENDIAN_BIG) |
| { |
| size_t i = 0; |
| |
| if (std::is_signed<T>::value) |
| { |
| /* Do the sign extension once at the start. */ |
| retval = ((LONGEST) buf[i] ^ 0x80) - 0x80; |
| ++i; |
| } |
| for (; i < buf.size (); ++i) |
| retval = (retval << 8) | buf[i]; |
| } |
| else |
| { |
| ssize_t i = buf.size () - 1; |
| |
| if (std::is_signed<T>::value) |
| { |
| /* Do the sign extension once at the start. */ |
| retval = ((LONGEST) buf[i] ^ 0x80) - 0x80; |
| --i; |
| } |
| for (; i >= 0; --i) |
| retval = (retval << 8) | buf[i]; |
| } |
| return retval; |
| } |
| |
| /* Explicit instantiations. */ |
| template LONGEST extract_integer<LONGEST> (gdb::array_view<const gdb_byte> buf, |
| enum bfd_endian byte_order); |
| template ULONGEST extract_integer<ULONGEST> |
| (gdb::array_view<const gdb_byte> buf, enum bfd_endian byte_order); |
| |
| /* Treat the bytes at BUF as a pointer of type TYPE, and return the |
| address it represents. */ |
| CORE_ADDR |
| extract_typed_address (const gdb_byte *buf, struct type *type) |
| { |
| gdb_assert (type->is_pointer_or_reference ()); |
| return gdbarch_pointer_to_address (type->arch (), type, buf); |
| } |
| |
| /* All 'store' functions accept a host-format integer and store a |
| target-format integer at ADDR which is LEN bytes long. */ |
| template<typename T, typename> |
| void |
| store_integer (gdb::array_view<gdb_byte> dst, enum bfd_endian byte_order, |
| T val) |
| { |
| gdb_byte *p; |
| gdb_byte *startaddr = dst.data (); |
| gdb_byte *endaddr = startaddr + dst.size (); |
| |
| /* Start at the least significant end of the integer, and work towards |
| the most significant. */ |
| if (byte_order == BFD_ENDIAN_BIG) |
| { |
| for (p = endaddr - 1; p >= startaddr; --p) |
| { |
| *p = val & 0xff; |
| val >>= 8; |
| } |
| } |
| else |
| { |
| for (p = startaddr; p < endaddr; ++p) |
| { |
| *p = val & 0xff; |
| val >>= 8; |
| } |
| } |
| } |
| |
| /* Explicit instantiations. */ |
| template void store_integer (gdb::array_view<gdb_byte> dst, |
| bfd_endian byte_order, LONGEST val); |
| |
| template void store_integer (gdb::array_view<gdb_byte> dst, |
| bfd_endian byte_order, ULONGEST val); |
| |
| /* Store the address ADDR as a pointer of type TYPE at BUF, in target |
| form. */ |
| void |
| store_typed_address (gdb_byte *buf, struct type *type, CORE_ADDR addr) |
| { |
| gdb_assert (type->is_pointer_or_reference ()); |
| gdbarch_address_to_pointer (type->arch (), type, buf, addr); |
| } |
| |
| /* Copy a value from SOURCE of size SOURCE_SIZE bytes to DEST of size DEST_SIZE |
| bytes. If SOURCE_SIZE is greater than DEST_SIZE, then truncate the most |
| significant bytes. If SOURCE_SIZE is less than DEST_SIZE then either sign |
| or zero extended according to IS_SIGNED. Values are stored in memory with |
| endianness BYTE_ORDER. */ |
| |
| void |
| copy_integer_to_size (gdb_byte *dest, int dest_size, const gdb_byte *source, |
| int source_size, bool is_signed, |
| enum bfd_endian byte_order) |
| { |
| signed int size_diff = dest_size - source_size; |
| |
| /* Copy across everything from SOURCE that can fit into DEST. */ |
| |
| if (byte_order == BFD_ENDIAN_BIG && size_diff > 0) |
| memcpy (dest + size_diff, source, source_size); |
| else if (byte_order == BFD_ENDIAN_BIG && size_diff < 0) |
| memcpy (dest, source - size_diff, dest_size); |
| else |
| memcpy (dest, source, std::min (source_size, dest_size)); |
| |
| /* Fill the remaining space in DEST by either zero extending or sign |
| extending. */ |
| |
| if (size_diff > 0) |
| { |
| gdb_byte extension = 0; |
| if (is_signed |
| && ((byte_order != BFD_ENDIAN_BIG && source[source_size - 1] & 0x80) |
| || (byte_order == BFD_ENDIAN_BIG && source[0] & 0x80))) |
| extension = 0xff; |
| |
| /* Extend into MSBs of SOURCE. */ |
| if (byte_order == BFD_ENDIAN_BIG) |
| memset (dest, extension, size_diff); |
| else |
| memset (dest + source_size, extension, size_diff); |
| } |
| } |
| |
| #if GDB_SELF_TEST |
| namespace selftests { |
| |
| /* Function to test copy_integer_to_size. Store SOURCE_VAL with size |
| SOURCE_SIZE to a buffer, making sure no sign extending happens at this |
| stage. Copy buffer to a new buffer using copy_integer_to_size. Extract |
| copied value and compare to DEST_VALU. Copy again with a signed |
| copy_integer_to_size and compare to DEST_VALS. Do everything for both |
| LITTLE and BIG target endians. Use unsigned values throughout to make |
| sure there are no implicit sign extensions. */ |
| |
| static void |
| do_cint_test (ULONGEST dest_valu, ULONGEST dest_vals, int dest_size, |
| ULONGEST src_val, int src_size) |
| { |
| for (int i = 0; i < 2 ; i++) |
| { |
| gdb_byte srcbuf[sizeof (ULONGEST)] = {}; |
| gdb_byte destbuf[sizeof (ULONGEST)] = {}; |
| enum bfd_endian byte_order = i ? BFD_ENDIAN_BIG : BFD_ENDIAN_LITTLE; |
| |
| /* Fill the src buffer (and later the dest buffer) with non-zero junk, |
| to ensure zero extensions aren't hidden. */ |
| memset (srcbuf, 0xaa, sizeof (srcbuf)); |
| |
| /* Store (and later extract) using unsigned to ensure there are no sign |
| extensions. */ |
| store_unsigned_integer (srcbuf, src_size, byte_order, src_val); |
| |
| /* Test unsigned. */ |
| memset (destbuf, 0xaa, sizeof (destbuf)); |
| copy_integer_to_size (destbuf, dest_size, srcbuf, src_size, false, |
| byte_order); |
| SELF_CHECK (dest_valu == extract_unsigned_integer (destbuf, dest_size, |
| byte_order)); |
| |
| /* Test signed. */ |
| memset (destbuf, 0xaa, sizeof (destbuf)); |
| copy_integer_to_size (destbuf, dest_size, srcbuf, src_size, true, |
| byte_order); |
| SELF_CHECK (dest_vals == extract_unsigned_integer (destbuf, dest_size, |
| byte_order)); |
| } |
| } |
| |
| static void |
| copy_integer_to_size_test () |
| { |
| /* Destination is bigger than the source, which has the signed bit unset. */ |
| do_cint_test (0x12345678, 0x12345678, 8, 0x12345678, 4); |
| do_cint_test (0x345678, 0x345678, 8, 0x12345678, 3); |
| |
| /* Destination is bigger than the source, which has the signed bit set. */ |
| do_cint_test (0xdeadbeef, 0xffffffffdeadbeef, 8, 0xdeadbeef, 4); |
| do_cint_test (0xadbeef, 0xffffffffffadbeef, 8, 0xdeadbeef, 3); |
| |
| /* Destination is smaller than the source. */ |
| do_cint_test (0x5678, 0x5678, 2, 0x12345678, 3); |
| do_cint_test (0xbeef, 0xbeef, 2, 0xdeadbeef, 3); |
| |
| /* Destination and source are the same size. */ |
| do_cint_test (0x8765432112345678, 0x8765432112345678, 8, 0x8765432112345678, |
| 8); |
| do_cint_test (0x432112345678, 0x432112345678, 6, 0x8765432112345678, 6); |
| do_cint_test (0xfeedbeaddeadbeef, 0xfeedbeaddeadbeef, 8, 0xfeedbeaddeadbeef, |
| 8); |
| do_cint_test (0xbeaddeadbeef, 0xbeaddeadbeef, 6, 0xfeedbeaddeadbeef, 6); |
| |
| /* Destination is bigger than the source. Source is bigger than 32bits. */ |
| do_cint_test (0x3412345678, 0x3412345678, 8, 0x3412345678, 6); |
| do_cint_test (0xff12345678, 0xff12345678, 8, 0xff12345678, 6); |
| do_cint_test (0x432112345678, 0x432112345678, 8, 0x8765432112345678, 6); |
| do_cint_test (0xff2112345678, 0xffffff2112345678, 8, 0xffffff2112345678, 6); |
| } |
| |
| template<typename T> |
| void |
| do_extract_test (gdb_byte byte1, gdb_byte byte2, enum bfd_endian endian, |
| std::optional<T> expected) |
| { |
| std::optional<T> result; |
| |
| try |
| { |
| const gdb_byte val[2] = { byte1, byte2 }; |
| result = extract_integer<T> (gdb::make_array_view (val, 2), endian); |
| } |
| catch (const gdb_exception_error &) |
| { |
| } |
| |
| SELF_CHECK (result == expected); |
| } |
| |
| template<typename T> |
| void |
| do_extract_tests (gdb_byte low, gdb_byte high, std::optional<T> expected) |
| { |
| do_extract_test (low, high, BFD_ENDIAN_LITTLE, expected); |
| do_extract_test (high, low, BFD_ENDIAN_BIG, expected); |
| } |
| |
| static void |
| extract_integer_test () |
| { |
| do_extract_tests<uint8_t> (0x00, 0xff, {}); |
| do_extract_tests<uint8_t> (0x7f, 0x23, {}); |
| do_extract_tests<uint8_t> (0x80, 0xff, {}); |
| do_extract_tests<uint8_t> (0x00, 0x00, 0x00); |
| |
| do_extract_tests<int8_t> (0xff, 0x00, 0xff); |
| do_extract_tests<int8_t> (0x7f, 0x23, {}); |
| do_extract_tests<int8_t> (0x80, 0xff, 0x80); |
| do_extract_tests<int8_t> (0x00, 0x00, 0x00); |
| } |
| |
| } // namespace selftests |
| |
| #endif |
| |
| void _initialize_extract_store_integer (); |
| void |
| _initialize_extract_store_integer () |
| { |
| #if GDB_SELF_TEST |
| selftests::register_test ("copy_integer_to_size", |
| selftests::copy_integer_to_size_test); |
| selftests::register_test ("extract_integer", |
| selftests::extract_integer_test); |
| #endif |
| } |