| /* Copyright (C) 2019-2021 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 "gmp-utils.h" |
| |
| /* See gmp-utils.h. */ |
| |
| std::string |
| gmp_string_printf (const char *fmt, ...) |
| { |
| va_list vp; |
| |
| va_start (vp, fmt); |
| int size = gmp_vsnprintf (NULL, 0, fmt, vp); |
| va_end (vp); |
| |
| std::string str (size, '\0'); |
| |
| /* C++11 and later guarantee std::string uses contiguous memory and |
| always includes the terminating '\0'. */ |
| va_start (vp, fmt); |
| gmp_vsprintf (&str[0], fmt, vp); |
| va_end (vp); |
| |
| return str; |
| } |
| |
| /* See gmp-utils.h. */ |
| |
| void |
| gdb_mpz::read (gdb::array_view<const gdb_byte> buf, enum bfd_endian byte_order, |
| bool unsigned_p) |
| { |
| mpz_import (val, 1 /* count */, -1 /* order */, buf.size () /* size */, |
| byte_order == BFD_ENDIAN_BIG ? 1 : -1 /* endian */, |
| 0 /* nails */, buf.data () /* op */); |
| |
| if (!unsigned_p) |
| { |
| /* The value was imported as if it was a positive value, |
| as mpz_import does not handle signs. If the original value |
| was in fact negative, we need to adjust VAL accordingly. */ |
| gdb_mpz max; |
| |
| mpz_ui_pow_ui (max.val, 2, buf.size () * HOST_CHAR_BIT - 1); |
| if (mpz_cmp (val, max.val) >= 0) |
| mpz_submul_ui (val, max.val, 2); |
| } |
| } |
| |
| /* See gmp-utils.h. */ |
| |
| void |
| gdb_mpz::write (gdb::array_view<gdb_byte> buf, enum bfd_endian byte_order, |
| bool unsigned_p) const |
| { |
| this->safe_export |
| (buf, byte_order == BFD_ENDIAN_BIG ? 1 : -1 /* endian */, unsigned_p); |
| } |
| |
| /* See gmp-utils.h. */ |
| |
| void |
| gdb_mpz::safe_export (gdb::array_view<gdb_byte> buf, |
| int endian, bool unsigned_p) const |
| { |
| gdb_assert (buf.size () > 0); |
| |
| if (mpz_sgn (val) == 0) |
| { |
| /* Our value is zero, so no need to call mpz_export to do the work, |
| especially since mpz_export's documentation explicitly says |
| that the function is a noop in this case. Just write zero to |
| BUF ourselves. */ |
| memset (buf.data (), 0, buf.size ()); |
| return; |
| } |
| |
| /* Determine the maximum range of values that our buffer can hold, |
| and verify that VAL is within that range. */ |
| |
| gdb_mpz lo, hi; |
| const size_t max_usable_bits = buf.size () * HOST_CHAR_BIT; |
| if (unsigned_p) |
| { |
| lo = 0; |
| |
| mpz_ui_pow_ui (hi.val, 2, max_usable_bits); |
| mpz_sub_ui (hi.val, hi.val, 1); |
| } |
| else |
| { |
| mpz_ui_pow_ui (lo.val, 2, max_usable_bits - 1); |
| mpz_neg (lo.val, lo.val); |
| |
| mpz_ui_pow_ui (hi.val, 2, max_usable_bits - 1); |
| mpz_sub_ui (hi.val, hi.val, 1); |
| } |
| |
| if (mpz_cmp (val, lo.val) < 0 || mpz_cmp (val, hi.val) > 0) |
| error (_("Cannot export value %s as %zu-bits %s integer" |
| " (must be between %s and %s)"), |
| this->str ().c_str (), |
| max_usable_bits, |
| unsigned_p ? _("unsigned") : _("signed"), |
| lo.str ().c_str (), |
| hi.str ().c_str ()); |
| |
| gdb_mpz exported_val (val); |
| |
| if (mpz_cmp_ui (exported_val.val, 0) < 0) |
| { |
| /* mpz_export does not handle signed values, so create a positive |
| value whose bit representation as an unsigned of the same length |
| would be the same as our negative value. */ |
| gdb_mpz neg_offset; |
| |
| mpz_ui_pow_ui (neg_offset.val, 2, buf.size () * HOST_CHAR_BIT); |
| mpz_add (exported_val.val, exported_val.val, neg_offset.val); |
| } |
| |
| /* Do the export into a buffer allocated by GMP itself; that way, |
| we can detect cases where BUF is not large enough to export |
| our value, and thus avoid a buffer overlow. Normally, this should |
| never happen, since we verified earlier that the buffer is large |
| enough to accomodate our value, but doing this allows us to be |
| extra safe with the export. |
| |
| After verification that the export behaved as expected, we will |
| copy the data over to BUF. */ |
| |
| size_t word_countp; |
| gdb::unique_xmalloc_ptr<void> exported |
| (mpz_export (NULL, &word_countp, -1 /* order */, buf.size () /* size */, |
| endian, 0 /* nails */, exported_val.val)); |
| |
| gdb_assert (word_countp == 1); |
| |
| memcpy (buf.data (), exported.get (), buf.size ()); |
| } |
| |
| /* See gmp-utils.h. */ |
| |
| gdb_mpz |
| gdb_mpq::get_rounded () const |
| { |
| /* Work with a positive number so as to make the "floor" rounding |
| always round towards zero. */ |
| |
| gdb_mpq abs_val (val); |
| mpq_abs (abs_val.val, abs_val.val); |
| |
| /* Convert our rational number into a quotient and remainder, |
| with "floor" rounding, which in our case means rounding |
| towards zero. */ |
| |
| gdb_mpz quotient, remainder; |
| mpz_fdiv_qr (quotient.val, remainder.val, |
| mpq_numref (abs_val.val), mpq_denref (abs_val.val)); |
| |
| /* Multiply the remainder by 2, and see if it is greater or equal |
| to abs_val's denominator. If yes, round to the next integer. */ |
| |
| mpz_mul_ui (remainder.val, remainder.val, 2); |
| if (mpz_cmp (remainder.val, mpq_denref (abs_val.val)) >= 0) |
| mpz_add_ui (quotient.val, quotient.val, 1); |
| |
| /* Re-apply the sign if needed. */ |
| if (mpq_sgn (val) < 0) |
| mpz_neg (quotient.val, quotient.val); |
| |
| return quotient; |
| } |
| |
| /* See gmp-utils.h. */ |
| |
| void |
| gdb_mpq::read_fixed_point (gdb::array_view<const gdb_byte> buf, |
| enum bfd_endian byte_order, bool unsigned_p, |
| const gdb_mpq &scaling_factor) |
| { |
| gdb_mpz vz; |
| vz.read (buf, byte_order, unsigned_p); |
| |
| mpq_set_z (val, vz.val); |
| mpq_mul (val, val, scaling_factor.val); |
| } |
| |
| /* See gmp-utils.h. */ |
| |
| void |
| gdb_mpq::write_fixed_point (gdb::array_view<gdb_byte> buf, |
| enum bfd_endian byte_order, bool unsigned_p, |
| const gdb_mpq &scaling_factor) const |
| { |
| gdb_mpq unscaled (val); |
| |
| mpq_div (unscaled.val, unscaled.val, scaling_factor.val); |
| |
| gdb_mpz unscaled_z = unscaled.get_rounded (); |
| unscaled_z.write (buf, byte_order, unsigned_p); |
| } |
| |
| /* A wrapper around xrealloc that we can then register with GMP |
| as the "realloc" function. */ |
| |
| static void * |
| xrealloc_for_gmp (void *ptr, size_t old_size, size_t new_size) |
| { |
| return xrealloc (ptr, new_size); |
| } |
| |
| /* A wrapper around xfree that we can then register with GMP |
| as the "free" function. */ |
| |
| static void |
| xfree_for_gmp (void *ptr, size_t size) |
| { |
| xfree (ptr); |
| } |
| |
| void _initialize_gmp_utils (); |
| |
| void |
| _initialize_gmp_utils () |
| { |
| /* Tell GMP to use GDB's memory management routines. */ |
| mp_set_memory_functions (xmalloc, xrealloc_for_gmp, xfree_for_gmp); |
| } |