diff --git a/include/m_ctype.h b/include/m_ctype.h
index a552226..1e7a47a 100644
--- a/include/m_ctype.h
+++ b/include/m_ctype.h
@@ -46,6 +46,19 @@ extern "C" {
 
 #define MY_CS_REPLACEMENT_CHARACTER 0xFFFD
 
+/**
+  Maximum character length of a string produced by wc_to_printable().
+  Note, wc_to_printable() is currently limited to BMP.
+  One non-printable or non-convertable character can produce a string
+  with at most 5 characters: \hhhh.
+  If we ever modify wc_to_printable() to support supplementary characters,
+  e.g. \+hhhhhh, this constant should be changed to 8.
+  Note, maximum octet length of a wc_to_printable() result can be calculated
+  as: (MY_CS_PRINTABLE_CHAR_LENGTH*cs->mbminlen).
+*/
+#define MY_CS_PRINTABLE_CHAR_LENGTH  5
+
+
 /*
   On i386 we store Unicode->CS conversion tables for
   some character sets using Big-endian order,
@@ -529,10 +542,12 @@ struct my_charset_handler_st
     cs->cset->native_to_mb() rather than cs->cset->wc_mb().
   */
   my_charset_conv_wc_mb native_to_mb;
+  my_charset_conv_wc_mb wc_to_printable;
 };
 
 extern MY_CHARSET_HANDLER my_charset_8bit_handler;
 extern MY_CHARSET_HANDLER my_charset_ucs2_handler;
+extern MY_CHARSET_HANDLER my_charset_utf8_handler;
 
 
 /*
@@ -684,6 +699,9 @@ int my_mb_wc_8bit(CHARSET_INFO *cs,my_wc_t *wc, const uchar *s,const uchar *e);
 int my_wc_mb_8bit(CHARSET_INFO *cs,my_wc_t wc, uchar *s, uchar *e);
 int my_wc_mb_bin(CHARSET_INFO *cs,my_wc_t wc, uchar *s, uchar *e);
 
+int my_wc_to_printable_8bit(CHARSET_INFO *cs, my_wc_t wc, uchar *s, uchar *e);
+int my_wc_to_printable_mb(CHARSET_INFO *cs, my_wc_t wc, uchar *s, uchar *e);
+
 int my_mb_ctype_8bit(CHARSET_INFO *,int *, const uchar *,const uchar *);
 int my_mb_ctype_mb(CHARSET_INFO *,int *, const uchar *,const uchar *);
 
@@ -889,6 +907,18 @@ uint32 my_convert(char *to, uint32 to_length, CHARSET_INFO *to_cs,
                   const char *from, uint32 from_length,
                   CHARSET_INFO *from_cs, uint *errors);
 
+/**
+  An extended version of my_convert(), to pass non-default mb_wc() and wc_mb().
+  For example, String::copy_printable() which is used in
+  Protocol::store_warning() uses this to escape control
+  and non-convertable characters.
+*/
+uint32 my_convert_using_func(char *to, uint32 to_length, CHARSET_INFO *to_cs,
+                             my_charset_conv_wc_mb mb_wc,
+                             const char *from, uint32 from_length,
+                             CHARSET_INFO *from_cs,
+                             my_charset_conv_mb_wc wc_mb,
+                             uint *errors);
 /*
   Convert a string between two character sets.
   Bad byte sequences as well as characters that cannot be
diff --git a/mysql-test/r/ctype_latin1.result b/mysql-test/r/ctype_latin1.result
index 4847592..271822d 100644
--- a/mysql-test/r/ctype_latin1.result
+++ b/mysql-test/r/ctype_latin1.result
@@ -8181,5 +8181,53 @@ Warnings:
 Note	1003	select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b`,`test`.`t1`.`c` AS `c`,`test`.`t1`.`d` AS `d` from `test`.`t1` where ((coalesce(`test`.`t1`.`c`,0) = '3 ') and (coalesce(`test`.`t1`.`d`,0) = '3 '))
 DROP TABLE t1;
 #
+# MDEV-8844 Unreadable control characters printed as is in warnings
+#
+SET NAMES latin1;
+SELECT CAST(_latin1 0x610062 AS INT);
+CAST(_latin1 0x610062 AS INT)
+0
+Warnings:
+Warning	1292	Truncated incorrect INTEGER value: 'a\0000b'
+SELECT CAST(_latin1 0x610162 AS INT);
+CAST(_latin1 0x610162 AS INT)
+0
+Warnings:
+Warning	1292	Truncated incorrect INTEGER value: 'a\0001b'
+SELECT CAST(_latin1 0x611F62 AS INT);
+CAST(_latin1 0x611F62 AS INT)
+0
+Warnings:
+Warning	1292	Truncated incorrect INTEGER value: 'a\001Fb'
+SELECT CAST(_latin1 0x617F62 AS INT);
+CAST(_latin1 0x617F62 AS INT)
+0
+Warnings:
+Warning	1292	Truncated incorrect INTEGER value: 'a\007Fb'
+SELECT CAST(_latin1 0x612062 AS INT);
+CAST(_latin1 0x612062 AS INT)
+0
+Warnings:
+Warning	1292	Truncated incorrect INTEGER value: 'a b'
+SELECT CAST(_latin1 0x617E62 AS INT);
+CAST(_latin1 0x617E62 AS INT)
+0
+Warnings:
+Warning	1292	Truncated incorrect INTEGER value: 'a~b'
+SELECT CAST(_latin1 0x61FF62 AS INT);
+CAST(_latin1 0x61FF62 AS INT)
+0
+Warnings:
+Warning	1292	Truncated incorrect INTEGER value: 'a�b'
+SET NAMES binary;
+CREATE TABLE t1 (a VARCHAR(20) CHARACTER SET latin1, UNIQUE(a));
+INSERT INTO t1 VALUES (0x61000162FF);
+INSERT INTO t1 VALUES (0x61000162FF);
+ERROR 23000: Duplicate entry 'a\0000\0001b��' for key 'a'
+INSERT IGNORE INTO t1 VALUES (0x61000162FF);
+Warnings:
+Warning	1062	Duplicate entry 'a\0000\0001b��' for key 'a'
+DROP TABLE t1;
+#
 # End of 10.1 tests
 #
diff --git a/mysql-test/r/ctype_ucs.result b/mysql-test/r/ctype_ucs.result
index 5617431..82e4784 100644
--- a/mysql-test/r/ctype_ucs.result
+++ b/mysql-test/r/ctype_ucs.result
@@ -5649,5 +5649,38 @@ CAST(CONVERT('1��3' USING ucs2) AS SIGNED)
 Warnings:
 Warning	1292	Truncated incorrect INTEGER value: '1��3'
 #
+# MDEV-8844 Unreadable control characters printed as is in warnings
+#
+SELECT CAST(_ucs2 0x006100000062 AS INT);
+CAST(_ucs2 0x006100000062 AS INT)
+0
+Warnings:
+Warning	1292	Truncated incorrect INTEGER value: 'a\0000b'
+SELECT CAST(_ucs2 0x006100010062 AS INT);
+CAST(_ucs2 0x006100010062 AS INT)
+0
+Warnings:
+Warning	1292	Truncated incorrect INTEGER value: 'a\0001b'
+SELECT CAST(_ucs2 0x0061D8000062 AS INT);
+CAST(_ucs2 0x0061D8000062 AS INT)
+0
+Warnings:
+Warning	1292	Truncated incorrect INTEGER value: 'a\D800b'
+SELECT CAST(_ucs2 0x0061DFFF0062 AS INT);
+CAST(_ucs2 0x0061DFFF0062 AS INT)
+0
+Warnings:
+Warning	1292	Truncated incorrect INTEGER value: 'a\DFFFb'
+SELECT CAST(_ucs2 0x0061D7000062 AS INT);
+CAST(_ucs2 0x0061D7000062 AS INT)
+0
+Warnings:
+Warning	1292	Truncated incorrect INTEGER value: 'a���b'
+SELECT CAST(_ucs2 0x0061E0030062 AS INT);
+CAST(_ucs2 0x0061E0030062 AS INT)
+0
+Warnings:
+Warning	1292	Truncated incorrect INTEGER value: 'a���b'
+#
 # End of 10.1 tests
 #
diff --git a/mysql-test/r/ctype_utf16.result b/mysql-test/r/ctype_utf16.result
index 3bd3725..303fa48 100644
--- a/mysql-test/r/ctype_utf16.result
+++ b/mysql-test/r/ctype_utf16.result
@@ -2199,5 +2199,14 @@ CAST(CONVERT('1��3' USING utf16) AS SIGNED)
 Warnings:
 Warning	1292	Truncated incorrect INTEGER value: '1��3'
 #
+# MDEV-8844 Unreadable control characters printed as is in warnings
+#
+SET NAMES utf8;
+SELECT CAST(_utf16 0x0061D83DDE0E0062 AS INT);
+CAST(_utf16 0x0061D83DDE0E0062 AS INT)
+0
+Warnings:
+Warning	1292	Truncated incorrect INTEGER value: 'a?b'
+#
 # End of 10.1 tests
 #
diff --git a/mysql-test/r/ctype_utf8.result b/mysql-test/r/ctype_utf8.result
index 66db7df..27886a2 100644
--- a/mysql-test/r/ctype_utf8.result
+++ b/mysql-test/r/ctype_utf8.result
@@ -5382,13 +5382,13 @@ EXPLAIN EXTENDED SELECT 'abcd����������', _latin1'abcd����������', _utf8'abcd��
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	filtered	Extra
 1	SIMPLE	NULL	NULL	NULL	NULL	NULL	NULL	NULL	NULL	No tables used
 Warnings:
-Note	1003	select 'abcd����������' AS `abcd����������`,_latin1'abcd\xC3\x81\xC3\x82\xC3\x83\xC3\x84\xC3\x85' AS `abcd����������`,_utf8'abcd\xC3\x81\xC3\x82\xC3\x83\xC3\x84\xC3\x85' AS `u`
+Note	1003	select 'abcd�\0081��������' AS `abcd�\0081��������`,_latin1'abcd\xC3\x81\xC3\x82\xC3\x83\xC3\x84\xC3\x85' AS `abcd�\0081��������`,_utf8'abcd\xC3\x81\xC3\x82\xC3\x83\xC3\x84\xC3\x85' AS `u`
 SET NAMES utf8;
 EXPLAIN EXTENDED SELECT 'abcd����������', _latin1'abcd����������', _utf8'abcd����������';
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	filtered	Extra
 1	SIMPLE	NULL	NULL	NULL	NULL	NULL	NULL	NULL	NULL	No tables used
 Warnings:
-Note	1003	select 'abcd����������' AS `abcd����������`,_latin1'abcd\xC3\x81\xC3\x82\xC3\x83\xC3\x84\xC3\x85' AS `abcd�����������������������`,_utf8'abcd\xC3\x81\xC3\x82\xC3\x83\xC3\x84\xC3\x85' AS `abcd����������`
+Note	1003	select 'abcd����������' AS `abcd����������`,_latin1'abcd\xC3\x81\xC3\x82\xC3\x83\xC3\x84\xC3\x85' AS `abcd��\0081�������������������`,_utf8'abcd\xC3\x81\xC3\x82\xC3\x83\xC3\x84\xC3\x85' AS `abcd����������`
 #
 # Bug#11750518 41090: ORDER BY TRUNCATES GROUP_CONCAT RESULT
 #
@@ -10213,5 +10213,78 @@ Warnings:
 Note	1003	select `test`.`t1`.`c` AS `c` from `test`.`t1` where (`test`.`t1`.`c` = 'A')
 DROP TABLE t1;
 #
+# MDEV-8844 Unreadable control characters printed as is in warnings
+#
+SET NAMES utf8;
+SELECT CAST(_utf8 0x610062 AS INT);
+CAST(_utf8 0x610062 AS INT)
+0
+Warnings:
+Warning	1292	Truncated incorrect INTEGER value: 'a\0000b'
+SELECT CAST(_utf8 0x610162 AS INT);
+CAST(_utf8 0x610162 AS INT)
+0
+Warnings:
+Warning	1292	Truncated incorrect INTEGER value: 'a\0001b'
+SELECT CAST(_utf8 0x611F62 AS INT);
+CAST(_utf8 0x611F62 AS INT)
+0
+Warnings:
+Warning	1292	Truncated incorrect INTEGER value: 'a\001Fb'
+SELECT CAST(_utf8 0x617F62 AS INT);
+CAST(_utf8 0x617F62 AS INT)
+0
+Warnings:
+Warning	1292	Truncated incorrect INTEGER value: 'a\007Fb'
+SELECT CAST(_utf8 0x61C28062 AS INT);
+CAST(_utf8 0x61C28062 AS INT)
+0
+Warnings:
+Warning	1292	Truncated incorrect INTEGER value: 'a\0080b'
+SELECT CAST(_utf8 0x61C29F62 AS INT);
+CAST(_utf8 0x61C29F62 AS INT)
+0
+Warnings:
+Warning	1292	Truncated incorrect INTEGER value: 'a\009Fb'
+SELECT CAST(_utf8 0x612062 AS INT);
+CAST(_utf8 0x612062 AS INT)
+0
+Warnings:
+Warning	1292	Truncated incorrect INTEGER value: 'a b'
+SELECT CAST(_utf8 0x617E62 AS INT);
+CAST(_utf8 0x617E62 AS INT)
+0
+Warnings:
+Warning	1292	Truncated incorrect INTEGER value: 'a~b'
+SELECT CAST(_utf8 0x61C2BF62 AS INT);
+CAST(_utf8 0x61C2BF62 AS INT)
+0
+Warnings:
+Warning	1292	Truncated incorrect INTEGER value: 'a��b'
+SELECT CAST(_utf8 '������' AS INT);
+CAST(_utf8 '������' AS INT)
+0
+Warnings:
+Warning	1292	Truncated incorrect INTEGER value: '������'
+SELECT CAST(_utf8 '������' AS INT);
+CAST(_utf8 '������' AS INT)
+0
+Warnings:
+Warning	1292	Truncated incorrect INTEGER value: '������'
+SELECT CAST(_utf8 '������' AS INT);
+CAST(_utf8 '������' AS INT)
+0
+Warnings:
+Warning	1292	Truncated incorrect INTEGER value: '������'
+SET NAMES binary;
+CREATE TABLE t1 (a VARCHAR(20) CHARACTER SET utf8, UNIQUE(a));
+INSERT INTO t1 VALUES (_latin1 0x61000162FF);
+INSERT INTO t1 VALUES (_latin1 0x61000162FF);
+ERROR 23000: Duplicate entry 'a\0000\0001b��' for key 'a'
+INSERT IGNORE INTO t1 VALUES (_latin1 0x61000162FF);
+Warnings:
+Warning	1062	Duplicate entry 'a\0000\0001b��' for key 'a'
+DROP TABLE t1;
+#
 # End of 10.1 tests
 #
diff --git a/mysql-test/r/func_str.result b/mysql-test/r/func_str.result
index 9bc8061..57e62d9 100644
--- a/mysql-test/r/func_str.result
+++ b/mysql-test/r/func_str.result
@@ -939,17 +939,17 @@ explain extended select length('\n\t\r\b\0\_\%\\');
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	filtered	Extra
 1	SIMPLE	NULL	NULL	NULL	NULL	NULL	NULL	NULL	NULL	No tables used
 Warnings:
-Note	1003	select length('\n	\r\0\\_\\%\\') AS `length('\n\t\r\b\0\_\%\\')`
+Note	1003	select length('\n	\r\0008\0\\_\\%\\') AS `length('\n\t\r\b\0\_\%\\')`
 explain extended select bit_length('\n\t\r\b\0\_\%\\');
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	filtered	Extra
 1	SIMPLE	NULL	NULL	NULL	NULL	NULL	NULL	NULL	NULL	No tables used
 Warnings:
-Note	1003	select bit_length('\n	\r\0\\_\\%\\') AS `bit_length('\n\t\r\b\0\_\%\\')`
+Note	1003	select bit_length('\n	\r\0008\0\\_\\%\\') AS `bit_length('\n\t\r\b\0\_\%\\')`
 explain extended select bit_length('\n\t\r\b\0\_\%\\');
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	filtered	Extra
 1	SIMPLE	NULL	NULL	NULL	NULL	NULL	NULL	NULL	NULL	No tables used
 Warnings:
-Note	1003	select bit_length('\n	\r\0\\_\\%\\') AS `bit_length('\n\t\r\b\0\_\%\\')`
+Note	1003	select bit_length('\n	\r\0008\0\\_\\%\\') AS `bit_length('\n\t\r\b\0\_\%\\')`
 explain extended select concat('monty',' was here ','again');
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	filtered	Extra
 1	SIMPLE	NULL	NULL	NULL	NULL	NULL	NULL	NULL	NULL	No tables used
diff --git a/mysql-test/r/get_diagnostics.result b/mysql-test/r/get_diagnostics.result
index 623d48c..e196c47 100644
--- a/mysql-test/r/get_diagnostics.result
+++ b/mysql-test/r/get_diagnostics.result
@@ -510,7 +510,7 @@ BEGIN
 SIGNAL SQLSTATE '77777' SET MYSQL_ERRNO = 1000, MESSAGE_TEXT='����������';
 END|
 CALL p1();
-ERROR 77777: ����������
+ERROR 77777: �\0081��������
 GET DIAGNOSTICS CONDITION 1
 @mysql_errno = MYSQL_ERRNO, @message_text = MESSAGE_TEXT,
 @returned_sqlstate = RETURNED_SQLSTATE, @class_origin = CLASS_ORIGIN;
@@ -520,6 +520,15 @@ SELECT @mysql_errno, @message_text, @returned_sqlstate, @class_origin;
 @returned_sqlstate	77777
 @class_origin	
 DROP PROCEDURE p1;
+SET NAMES utf8;
+CREATE PROCEDURE p1()
+BEGIN
+SIGNAL SQLSTATE '77777' SET MYSQL_ERRNO = 1000, MESSAGE_TEXT='����������';
+END|
+CALL p1();
+ERROR 77777: ����������
+DROP PROCEDURE p1;
+SET NAMES latin1;
 CREATE PROCEDURE p1()
 BEGIN
 DECLARE cond CONDITION FOR SQLSTATE '12345';
diff --git a/mysql-test/r/not_embedded_server.result b/mysql-test/r/not_embedded_server.result
index 1cd54b1..50999ec 100644
--- a/mysql-test/r/not_embedded_server.result
+++ b/mysql-test/r/not_embedded_server.result
@@ -66,7 +66,7 @@ ERROR HY000: Operation CREATE USER failed for 'user\"s_12601974'@'localhost'
 DROP USER 'user\"s_12601974'@'localhost';
 CREATE USER 'user\bs_12601974'@'localhost';
 CREATE USER 'user\bs_12601974'@'localhost';
-ERROR HY000: Operation CREATE USER failed for 'users_12601974'@'localhost'
+ERROR HY000: Operation CREATE USER failed for 'user\0008s_12601974'@'localhost'
 DROP USER 'user\bs_12601974'@'localhost';
 CREATE USER 'user\ns_12601974'@'localhost';
 CREATE USER 'user\ns_12601974'@'localhost';
diff --git a/mysql-test/t/ctype_latin1.test b/mysql-test/t/ctype_latin1.test
index a30c7ae..cceaa84 100644
--- a/mysql-test/t/ctype_latin1.test
+++ b/mysql-test/t/ctype_latin1.test
@@ -374,5 +374,27 @@ SELECT * FROM t1 WHERE COALESCE(c,0)='3 ' AND COALESCE(d,0)=COALESCE(c,0);
 DROP TABLE t1;
 
 --echo #
+--echo # MDEV-8844 Unreadable control characters printed as is in warnings
+--echo #
+SET NAMES latin1;
+# control
+SELECT CAST(_latin1 0x610062 AS INT);
+SELECT CAST(_latin1 0x610162 AS INT);
+SELECT CAST(_latin1 0x611F62 AS INT);
+SELECT CAST(_latin1 0x617F62 AS INT);
+# normal characters
+SELECT CAST(_latin1 0x612062 AS INT);
+SELECT CAST(_latin1 0x617E62 AS INT);
+SELECT CAST(_latin1 0x61FF62 AS INT);
+
+SET NAMES binary;
+CREATE TABLE t1 (a VARCHAR(20) CHARACTER SET latin1, UNIQUE(a));
+INSERT INTO t1 VALUES (0x61000162FF);
+--error ER_DUP_ENTRY
+INSERT INTO t1 VALUES (0x61000162FF);
+INSERT IGNORE INTO t1 VALUES (0x61000162FF);
+DROP TABLE t1;
+
+--echo #
 --echo # End of 10.1 tests
 --echo #
diff --git a/mysql-test/t/ctype_ucs.test b/mysql-test/t/ctype_ucs.test
index 2f48062..d6341fb 100644
--- a/mysql-test/t/ctype_ucs.test
+++ b/mysql-test/t/ctype_ucs.test
@@ -955,5 +955,18 @@ SET NAMES utf8;
 SELECT CAST(CONVERT('1��3' USING ucs2) AS SIGNED);
 
 --echo #
+--echo # MDEV-8844 Unreadable control characters printed as is in warnings
+--echo #
+# control
+SELECT CAST(_ucs2 0x006100000062 AS INT);
+SELECT CAST(_ucs2 0x006100010062 AS INT);
+# surrogate halfs
+SELECT CAST(_ucs2 0x0061D8000062 AS INT);
+SELECT CAST(_ucs2 0x0061DFFF0062 AS INT);
+# normal characters
+SELECT CAST(_ucs2 0x0061D7000062 AS INT);
+SELECT CAST(_ucs2 0x0061E0030062 AS INT);
+
+--echo #
 --echo # End of 10.1 tests
 --echo #
diff --git a/mysql-test/t/ctype_utf16.test b/mysql-test/t/ctype_utf16.test
index bb7eb8c..9e15961 100644
--- a/mysql-test/t/ctype_utf16.test
+++ b/mysql-test/t/ctype_utf16.test
@@ -893,5 +893,14 @@ SELECT CAST(CONVERT('1��3' USING utf16) AS SIGNED);
 
 
 --echo #
+--echo # MDEV-8844 Unreadable control characters printed as is in warnings
+--echo #
+SET NAMES utf8;
+# Make sure surrogate halfs (when a part of a full utf16 character)
+# are not escaped and the entire utf16 character consisting of two
+# surrogate pairs is replaced to a single question mark.
+SELECT CAST(_utf16 0x0061D83DDE0E0062 AS INT);
+
+--echo #
 --echo # End of 10.1 tests
 --echo #
diff --git a/mysql-test/t/ctype_utf8.test b/mysql-test/t/ctype_utf8.test
index 639f6d4..a9fa0f6 100644
--- a/mysql-test/t/ctype_utf8.test
+++ b/mysql-test/t/ctype_utf8.test
@@ -1843,6 +1843,33 @@ EXPLAIN EXTENDED
 SELECT * FROM t1 WHERE c>=_utf8'a' COLLATE utf8_general_ci AND c='A';
 DROP TABLE t1;
 
+--echo #
+--echo # MDEV-8844 Unreadable control characters printed as is in warnings
+--echo #
+SET NAMES utf8;
+# control, part1
+SELECT CAST(_utf8 0x610062 AS INT);
+SELECT CAST(_utf8 0x610162 AS INT);
+SELECT CAST(_utf8 0x611F62 AS INT);
+# control, part2: U+0080..U+009F
+SELECT CAST(_utf8 0x617F62 AS INT);
+SELECT CAST(_utf8 0x61C28062 AS INT);
+SELECT CAST(_utf8 0x61C29F62 AS INT);
+# normal characters
+SELECT CAST(_utf8 0x612062 AS INT);
+SELECT CAST(_utf8 0x617E62 AS INT);
+SELECT CAST(_utf8 0x61C2BF62 AS INT);
+SELECT CAST(_utf8 '������' AS INT);
+SELECT CAST(_utf8 '������' AS INT);
+SELECT CAST(_utf8 '������' AS INT);
+
+SET NAMES binary;
+CREATE TABLE t1 (a VARCHAR(20) CHARACTER SET utf8, UNIQUE(a));
+INSERT INTO t1 VALUES (_latin1 0x61000162FF);
+--error ER_DUP_ENTRY
+INSERT INTO t1 VALUES (_latin1 0x61000162FF);
+INSERT IGNORE INTO t1 VALUES (_latin1 0x61000162FF);
+DROP TABLE t1;
 
 --echo #
 --echo # End of 10.1 tests
diff --git a/mysql-test/t/get_diagnostics.test b/mysql-test/t/get_diagnostics.test
index 0b0d8a7..8c9a169 100644
--- a/mysql-test/t/get_diagnostics.test
+++ b/mysql-test/t/get_diagnostics.test
@@ -556,6 +556,7 @@ DELIMITER ;|
 --error 1000
 CALL p1();
 
+
 GET DIAGNOSTICS CONDITION 1
   @mysql_errno = MYSQL_ERRNO, @message_text = MESSAGE_TEXT,
   @returned_sqlstate = RETURNED_SQLSTATE, @class_origin = CLASS_ORIGIN;
@@ -566,6 +567,18 @@ SELECT @mysql_errno, @message_text, @returned_sqlstate, @class_origin;
 
 DROP PROCEDURE p1;
 
+SET NAMES utf8;
+DELIMITER |;
+CREATE PROCEDURE p1()
+BEGIN
+  SIGNAL SQLSTATE '77777' SET MYSQL_ERRNO = 1000, MESSAGE_TEXT='����������';
+END|
+DELIMITER ;|
+--error 1000
+CALL p1();
+DROP PROCEDURE p1;
+SET NAMES latin1;
+
 DELIMITER |;
 CREATE PROCEDURE p1()
 BEGIN
diff --git a/sql/protocol.cc b/sql/protocol.cc
index fa42915..91b78be 100644
--- a/sql/protocol.cc
+++ b/sql/protocol.cc
@@ -1019,6 +1019,18 @@ bool Protocol::store_string_aux(const char *from, size_t length,
 }
 
 
+bool Protocol::store_warning(const char *from, size_t length)
+{
+  StringBuffer<MYSQL_ERRMSG_SIZE> tmp;
+  CHARSET_INFO *cs= thd->variables.character_set_results;
+  if (cs == &my_charset_bin)
+    cs= system_charset_info;
+  if (tmp.copy_printable_hhhh(cs, system_charset_info, from, length))
+    return net_store_data((const uchar*)"", 0);
+  return net_store_data((const uchar *) tmp.ptr(), tmp.length());
+}
+
+
 bool Protocol_text::store(const char *from, size_t length,
                           CHARSET_INFO *fromcs, CHARSET_INFO *tocs)
 {
diff --git a/sql/protocol.h b/sql/protocol.h
index ea33c6b..e1179f5 100644
--- a/sql/protocol.h
+++ b/sql/protocol.h
@@ -81,6 +81,7 @@ class Protocol
 
   bool store(I_List<i_string> *str_list);
   bool store(const char *from, CHARSET_INFO *cs);
+  bool store_warning(const char *from, size_t length);
   String *storage_packet() { return packet; }
   inline void free() { packet->free(); }
   virtual bool write();
diff --git a/sql/sql_error.cc b/sql/sql_error.cc
index b72d642..8af0e71 100644
--- a/sql/sql_error.cc
+++ b/sql/sql_error.cc
@@ -859,9 +859,8 @@ bool mysqld_show_warnings(THD *thd, ulong levels_to_show)
 		    warning_level_names[err->get_level()].length,
                     system_charset_info);
     protocol->store((uint32) err->get_sql_errno());
-    protocol->store(err->get_message_text(),
-                    err->get_message_octet_length(),
-                    system_charset_info);
+    protocol->store_warning(err->get_message_text(),
+                            err->get_message_octet_length());
     if (protocol->write())
       DBUG_RETURN(TRUE);
   }
@@ -874,6 +873,26 @@ bool mysqld_show_warnings(THD *thd, ulong levels_to_show)
 
 
 /**
+  This replaces U+0000 to '\0000', so the result error message string:
+  - is a good null-terminated string
+  - presents the entire data
+  For example:
+    SELECT CAST(_latin1 0x610062 AS SIGNED);
+  returns a warning:
+    Truncated incorrect INTEGER value: 'a\0000b'
+  Notice, the 0x00 byte is replaced to a 5-byte long string '\0000',
+  while 'a' and 'b' are printed as is.
+*/
+extern "C" int my_wc_mb_utf8_null_terminated(CHARSET_INFO *cs,
+                                             my_wc_t wc, uchar *r, uchar *e)
+{
+  return wc == '\0' ?
+         my_charset_utf8_handler.wc_to_printable(cs, wc, r, e) :
+         my_charset_utf8_handler.wc_mb(cs, wc, r, e);
+}
+
+
+/**
    Convert value for dispatch to error message(see WL#751).
 
    @param to          buffer for converted string
@@ -931,8 +950,11 @@ char *err_conv(char *buff, uint to_length, const char *from,
   else
   {
     uint errors;
-    res= copy_and_convert(to, to_length, system_charset_info,
-                          from, from_length, from_cs, &errors);
+    res= my_convert_using_func(to, to_length, system_charset_info,
+                               my_wc_mb_utf8_null_terminated,
+                               from, from_length,
+                               from_cs, from_cs->cset->mb_wc,
+                               &errors);
     to[res]= 0;
   }
   return buff;
@@ -958,64 +980,19 @@ uint32 convert_error_message(char *to, uint32 to_length, CHARSET_INFO *to_cs,
                              const char *from, uint32 from_length,
                              CHARSET_INFO *from_cs, uint *errors)
 {
-  int         cnvres;
-  my_wc_t     wc;
-  const uchar *from_end= (const uchar*) from+from_length;
-  char *to_start= to;
-  uchar *to_end;
-  my_charset_conv_mb_wc mb_wc= from_cs->cset->mb_wc;
-  my_charset_conv_wc_mb wc_mb;
-  uint error_count= 0;
-  uint length;
-
   DBUG_ASSERT(to_length > 0);
   /* Make room for the null terminator. */
   to_length--;
-  to_end= (uchar*) (to + to_length);
-
-  if (!to_cs || from_cs == to_cs || to_cs == &my_charset_bin)
-  {
-    length= MY_MIN(to_length, from_length);
-    memmove(to, from, length);
-    to[length]= 0;
-    return length;
-  }
-
-  wc_mb= to_cs->cset->wc_mb;
-  while (1)
-  {
-    if ((cnvres= (*mb_wc)(from_cs, &wc, (uchar*) from, from_end)) > 0)
-    {
-      if (!wc)
-        break;
-      from+= cnvres;
-    }
-    else if (cnvres == MY_CS_ILSEQ)
-    {
-      wc= (ulong) (uchar) *from;
-      from+=1;
-    }
-    else
-      break;
-
-    if ((cnvres= (*wc_mb)(to_cs, wc, (uchar*) to, to_end)) > 0)
-      to+= cnvres;
-    else if (cnvres == MY_CS_ILUNI)
-    {
-      length= (wc <= 0xFFFF) ? 6/* '\1234' format*/ : 9 /* '\+123456' format*/;
-      if ((uchar*)(to + length) >= to_end)
-        break;
-      cnvres= my_snprintf(to, 9,
-                          (wc <= 0xFFFF) ? "\\%04X" : "\\+%06X", (uint) wc);
-      to+= cnvres;
-    }
-    else
-      break;
-  }
-
-  *to= 0;
-  *errors= error_count;
-  return (uint32) (to - to_start);
+  if (to_cs == &my_charset_bin)
+    to_cs= system_charset_info;
+  uint32 cnv_length= my_convert_using_func(to, to_length,
+                                           to_cs, to_cs->cset->wc_to_printable,
+                                           from, from_length,
+                                           from_cs, from_cs->cset->mb_wc,
+                                           errors);
+  DBUG_ASSERT(to_length >= cnv_length);
+  to[cnv_length]= '\0';
+  return cnv_length;
 }
 
 
diff --git a/sql/sql_string.h b/sql/sql_string.h
index 51a11c7..446caed 100644
--- a/sql/sql_string.h
+++ b/sql/sql_string.h
@@ -417,6 +417,31 @@ class String
     str_charset= tocs;
     return false;
   }
+  /**
+    Convert a string to a printable format.
+    All non-convertable and control characters are replaced to 5-character
+    sequences '\hhhh'.
+  */
+  bool copy_printable_hhhh(CHARSET_INFO *to_cs,
+                           CHARSET_INFO *from_cs,
+                           const char *from, uint32 from_length)
+  {
+    uint errors;
+    uint one_escaped_char_length= MY_CS_PRINTABLE_CHAR_LENGTH * to_cs->mbminlen;
+    uint one_char_length= MY_MAX(one_escaped_char_length, to_cs->mbmaxlen);
+    uint32 bytes_needed= from_length * one_char_length;
+    if (alloc(bytes_needed))
+      return true;
+    str_charset= to_cs;
+    str_length= my_convert_using_func(Ptr, Alloced_length, to_cs,
+                                      to_cs->cset->wc_to_printable,
+                                      from, from_length,
+                                      from_cs,
+                                      from_cs->cset->mb_wc,
+                                      &errors);
+    return false;
+  }
+
   void move(String &s)
   {
     free();
diff --git a/strings/ctype-big5.c b/strings/ctype-big5.c
index d6a9695..9103fe5 100644
--- a/strings/ctype-big5.c
+++ b/strings/ctype-big5.c
@@ -6848,6 +6848,7 @@ static MY_CHARSET_HANDLER my_charset_big5_handler=
   my_well_formed_char_length_big5,
   my_copy_fix_mb,
   my_native_to_mb_big5,
+  my_wc_to_printable_8bit,
 };
 
 struct charset_info_st my_charset_big5_chinese_ci=
diff --git a/strings/ctype-bin.c b/strings/ctype-bin.c
index 0be6ae9..dd6c40e 100644
--- a/strings/ctype-bin.c
+++ b/strings/ctype-bin.c
@@ -551,6 +551,7 @@ static MY_CHARSET_HANDLER my_charset_handler=
   my_well_formed_char_length_8bit,
   my_copy_8bit,
   my_wc_mb_bin,
+  my_wc_mb_bin,
 };
 
 
diff --git a/strings/ctype-cp932.c b/strings/ctype-cp932.c
index 9bf206f..af60059 100644
--- a/strings/ctype-cp932.c
+++ b/strings/ctype-cp932.c
@@ -34723,6 +34723,7 @@ static MY_CHARSET_HANDLER my_charset_handler=
   my_well_formed_char_length_cp932,
   my_copy_fix_mb,
   my_native_to_mb_cp932,
+  my_wc_to_printable_8bit,
 };
 
 
diff --git a/strings/ctype-euc_kr.c b/strings/ctype-euc_kr.c
index 1f13ab6..e4fa6f8 100644
--- a/strings/ctype-euc_kr.c
+++ b/strings/ctype-euc_kr.c
@@ -10017,6 +10017,7 @@ static MY_CHARSET_HANDLER my_charset_handler=
   my_well_formed_char_length_euckr,
   my_copy_fix_mb,
   my_native_to_mb_euckr,
+  my_wc_to_printable_8bit,
 };
 
 
diff --git a/strings/ctype-eucjpms.c b/strings/ctype-eucjpms.c
index 82c4bb5..d1b498a 100644
--- a/strings/ctype-eucjpms.c
+++ b/strings/ctype-eucjpms.c
@@ -67550,6 +67550,7 @@ static MY_CHARSET_HANDLER my_charset_handler=
     my_well_formed_char_length_eucjpms,
     my_copy_fix_mb,
     my_native_to_mb_eucjpms,
+    my_wc_to_printable_8bit,
 };
 
 
diff --git a/strings/ctype-gb2312.c b/strings/ctype-gb2312.c
index b0e275f..873dacc 100644
--- a/strings/ctype-gb2312.c
+++ b/strings/ctype-gb2312.c
@@ -6421,6 +6421,7 @@ static MY_CHARSET_HANDLER my_charset_handler=
   my_well_formed_char_length_gb2312,
   my_copy_fix_mb,
   my_native_to_mb_gb2312,
+  my_wc_to_printable_8bit,
 };
 
 
diff --git a/strings/ctype-gbk.c b/strings/ctype-gbk.c
index 37b003f..9cd8f32 100644
--- a/strings/ctype-gbk.c
+++ b/strings/ctype-gbk.c
@@ -10733,6 +10733,7 @@ static MY_CHARSET_HANDLER my_charset_handler=
   my_well_formed_char_length_gbk,
   my_copy_fix_mb,
   my_native_to_mb_gbk,
+  my_wc_to_printable_8bit,
 };
 
 
diff --git a/strings/ctype-latin1.c b/strings/ctype-latin1.c
index 26c66d6..76216b5 100644
--- a/strings/ctype-latin1.c
+++ b/strings/ctype-latin1.c
@@ -426,6 +426,7 @@ static MY_CHARSET_HANDLER my_charset_handler=
     my_well_formed_char_length_8bit,
     my_copy_8bit,
     my_wc_mb_bin, /* native_to_mb */
+    my_wc_to_printable_8bit,
 };
 
 
diff --git a/strings/ctype-mb.c b/strings/ctype-mb.c
index eef283d..79ddf10 100644
--- a/strings/ctype-mb.c
+++ b/strings/ctype-mb.c
@@ -1543,4 +1543,116 @@ int my_mb_ctype_mb(CHARSET_INFO *cs, int *ctype,
 }
 
 
+/**
+  Detect if a Unicode code point is printable.
+*/
+static inline my_bool
+my_is_printable(my_wc_t wc)
+{
+  /*
+    Blocks:
+      U+0000 .. U+001F     control
+      U+0020 .. U+007E     printable
+      U+007F .. U+009F     control
+      U+00A0 .. U+00FF     printable
+      U+0100 .. U+10FFFF   As of Unicode-6.1.0, this range does not have any
+                           characters of the "Cc" (Other, control) category.
+                           Should be mostly safe to print.
+                           Except for the surrogate halfs,
+                           which are encoding components, not real characters.
+  */
+  if (wc >= 0x20 && wc <= 0x7E) /* Quickly detect ASCII printable */
+    return TRUE;
+  if (wc <= 0x9F)    /* The rest of U+0000..U+009F are control characters */
+  {
+    /* NL, CR, TAB are Ok */
+    return (wc == '\r' || wc == '\n' || wc == '\t');
+  }
+  /*
+    Surrogate halfs (when alone) print badly in terminals:
+      SELECT _ucs2 0xD800;
+    Let's escape them as well.
+  */
+  if (wc >= 0xD800 && wc <= 0xDFFF)
+    return FALSE;
+  return TRUE;
+}
+
+
+static uint to_printable_8bit(uchar *dst, my_wc_t wc)
+{
+  /*
+    This function is used only in context of error messages for now.
+    All non-BMP characters are currently replaced to question marks
+    when a message is put into diagnostics area.
+  */
+  DBUG_ASSERT(wc < 0x10000);
+  *dst++= '\\';
+  *dst++= _dig_vec_upper[(wc >> 12) & 0x0F];
+  *dst++= _dig_vec_upper[(wc >> 8) & 0x0F];
+  *dst++= _dig_vec_upper[(wc >> 4) & 0x0F];
+  *dst++= _dig_vec_upper[wc & 0x0F];
+  return MY_CS_PRINTABLE_CHAR_LENGTH;
+}
+
+
+/**
+  Encode an Unicode character "wc" into a printable string.
+  Non-convertable and non-printable code points are printed as \hhhh.
+  Convertable printable code points are encoded normally.
+  This function is suitable for 8-bit character sets,
+  as well as for ASCII-compatible multi-byte character sets, e.g. sjis, utf8.
+*/
+int
+my_wc_to_printable_8bit(CHARSET_INFO *cs, my_wc_t wc, uchar *str, uchar *end)
+{
+  if (my_is_printable(wc))
+  {
+    int mblen= cs->cset->wc_mb(cs, wc, str, end);
+    if (mblen > 0)
+      return mblen;
+  }
+  if (str + MY_CS_PRINTABLE_CHAR_LENGTH > end)
+    return MY_CS_TOOSMALLN(MY_CS_PRINTABLE_CHAR_LENGTH);
+  return to_printable_8bit(str, wc);
+}
+
+
+/**
+  Encode an Unicode character "wc" into a printable string.
+  This function is suitable for any character set, including
+  ASCII-incompatible multi-byte character sets, e.g. ucs2, utf16, utf32.
+*/
+int
+my_wc_to_printable_mb(CHARSET_INFO *cs, my_wc_t wc, uchar *str, uchar *end)
+{
+  uchar *str0;
+  uint i, length;
+  uchar tmp[MY_CS_PRINTABLE_CHAR_LENGTH];
+
+  if (my_is_printable(wc))
+  {
+    int mblen= cs->cset->wc_mb(cs, wc, str, end);
+    if (mblen > 0)
+      return mblen;
+  }
+
+  if (str + MY_CS_PRINTABLE_CHAR_LENGTH * cs->mbminlen > end)
+    return MY_CS_TOOSMALLN(MY_CS_PRINTABLE_CHAR_LENGTH * cs->mbminlen);
+
+  length= to_printable_8bit(tmp, wc);
+  str0= str;
+  for (i= 0; i < length; i++)
+  {
+    if (cs->cset->wc_mb(cs, tmp[i], str, end) != (int) cs->mbminlen)
+    {
+      DBUG_ASSERT(0);
+      return MY_CS_ILSEQ;
+    }
+    str+= cs->mbminlen;
+  }
+  return str - str0;
+}
+
+
 #endif
diff --git a/strings/ctype-simple.c b/strings/ctype-simple.c
index 288f5fd..31393d6 100644
--- a/strings/ctype-simple.c
+++ b/strings/ctype-simple.c
@@ -1956,6 +1956,7 @@ MY_CHARSET_HANDLER my_charset_8bit_handler=
     my_well_formed_char_length_8bit,
     my_copy_8bit,
     my_wc_mb_bin, /* native_to_mb */
+    my_wc_to_printable_8bit,
 };
 
 MY_COLLATION_HANDLER my_collation_8bit_simple_ci_handler =
diff --git a/strings/ctype-sjis.c b/strings/ctype-sjis.c
index 629e1cd..c0bcf8b 100644
--- a/strings/ctype-sjis.c
+++ b/strings/ctype-sjis.c
@@ -34102,6 +34102,7 @@ static MY_CHARSET_HANDLER my_charset_handler=
   my_well_formed_char_length_sjis,
   my_copy_fix_mb,
   my_native_to_mb_sjis,
+  my_wc_to_printable_8bit,
 };
 
 
diff --git a/strings/ctype-tis620.c b/strings/ctype-tis620.c
index a1ca320..6cef472 100644
--- a/strings/ctype-tis620.c
+++ b/strings/ctype-tis620.c
@@ -890,6 +890,7 @@ static MY_CHARSET_HANDLER my_charset_handler=
     my_well_formed_char_length_8bit,
     my_copy_8bit,
     my_wc_mb_bin, /* native_to_mb */
+    my_wc_to_printable_8bit,
 };
 
 
diff --git a/strings/ctype-ucs2.c b/strings/ctype-ucs2.c
index cae85f3..c5cbdb7 100644
--- a/strings/ctype-ucs2.c
+++ b/strings/ctype-ucs2.c
@@ -1606,6 +1606,7 @@ MY_CHARSET_HANDLER my_charset_utf16_handler=
   my_well_formed_char_length_utf16,
   my_copy_fix_mb2_or_mb4,
   my_uni_utf16,
+  my_wc_to_printable_mb,
 };
 
 
@@ -1829,6 +1830,7 @@ static MY_CHARSET_HANDLER my_charset_utf16le_handler=
   my_well_formed_char_length_utf16,
   my_copy_fix_mb2_or_mb4,
   my_uni_utf16le,
+  my_wc_to_printable_mb,
 };
 
 
@@ -2575,6 +2577,7 @@ MY_CHARSET_HANDLER my_charset_utf32_handler=
   my_well_formed_char_length_utf32,
   my_copy_fix_mb2_or_mb4,
   my_uni_utf32,
+  my_wc_to_printable_mb,
 };
 
 
@@ -3062,6 +3065,7 @@ MY_CHARSET_HANDLER my_charset_ucs2_handler=
     my_well_formed_char_length_ucs2,
     my_copy_fix_mb2_or_mb4,
     my_uni_ucs2,
+    my_wc_to_printable_mb,
 };
 
 
diff --git a/strings/ctype-ujis.c b/strings/ctype-ujis.c
index 308f5f0..432a330 100644
--- a/strings/ctype-ujis.c
+++ b/strings/ctype-ujis.c
@@ -67294,6 +67294,7 @@ static MY_CHARSET_HANDLER my_charset_handler=
     my_well_formed_char_length_ujis,
     my_copy_fix_mb,
     my_native_to_mb_ujis,
+    my_wc_to_printable_8bit,
 };
 
 
diff --git a/strings/ctype-utf8.c b/strings/ctype-utf8.c
index 3c2c812..5201c53 100644
--- a/strings/ctype-utf8.c
+++ b/strings/ctype-utf8.c
@@ -5527,6 +5527,7 @@ MY_CHARSET_HANDLER my_charset_utf8_handler=
     my_well_formed_char_length_utf8,
     my_copy_fix_mb,
     my_uni_utf8,
+    my_wc_to_printable_8bit,
 };
 
 
@@ -7111,6 +7112,7 @@ static MY_CHARSET_HANDLER my_charset_filename_handler=
     my_well_formed_char_length_filename,
     my_copy_fix_mb,
     my_wc_mb_filename,
+    my_wc_mb_filename, /* Is already printable */
 };
 
 
@@ -7882,6 +7884,7 @@ MY_CHARSET_HANDLER my_charset_utf8mb4_handler=
   my_well_formed_char_length_utf8mb4,
   my_copy_fix_mb,
   my_wc_mb_utf8mb4,
+  my_wc_to_printable_8bit,
 };
 
 
diff --git a/strings/ctype.c b/strings/ctype.c
index f871a21..620c7e1 100644
--- a/strings/ctype.c
+++ b/strings/ctype.c
@@ -1030,19 +1030,18 @@ my_charset_is_ascii_compatible(CHARSET_INFO *cs)
   @return Number of bytes copied to 'to' string
 */
 
-static uint32
-my_convert_internal(char *to, uint32 to_length,
-                    CHARSET_INFO *to_cs,
-                    const char *from, uint32 from_length,
-                    CHARSET_INFO *from_cs, uint *errors)
+uint32
+my_convert_using_func(char *to, uint32 to_length,
+                      CHARSET_INFO *to_cs, my_charset_conv_wc_mb wc_mb,
+                      const char *from, uint32 from_length,
+                      CHARSET_INFO *from_cs, my_charset_conv_mb_wc mb_wc,
+                      uint *errors)
 {
   int         cnvres;
   my_wc_t     wc;
   const uchar *from_end= (const uchar*) from + from_length;
   char *to_start= to;
   uchar *to_end= (uchar*) to + to_length;
-  my_charset_conv_mb_wc mb_wc= from_cs->cset->mb_wc;
-  my_charset_conv_wc_mb wc_mb= to_cs->cset->wc_mb;
   uint error_count= 0;
 
   while (1)
@@ -1119,8 +1118,11 @@ my_convert(char *to, uint32 to_length, CHARSET_INFO *to_cs,
     immediately switch to slow mb_wc->wc_mb method.
   */
   if ((to_cs->state | from_cs->state) & MY_CS_NONASCII)
-    return my_convert_internal(to, to_length, to_cs,
-                               from, from_length, from_cs, errors);
+    return my_convert_using_func(to, to_length,
+                                 to_cs, to_cs->cset->wc_mb,
+                                 from, from_length,
+                                 from_cs, from_cs->cset->mb_wc,
+                                 errors);
 
   length= length2= MY_MIN(to_length, from_length);
 
@@ -1152,9 +1154,11 @@ my_convert(char *to, uint32 to_length, CHARSET_INFO *to_cs,
       uint32 copied_length= length2 - length;
       to_length-= copied_length;
       from_length-= copied_length;
-      return copied_length + my_convert_internal(to, to_length, to_cs,
-                                                 from, from_length, from_cs,
-                                                 errors);
+      return copied_length + my_convert_using_func(to, to_length, to_cs,
+                                                   to_cs->cset->wc_mb,
+                                                   from, from_length, from_cs,
+                                                   from_cs->cset->mb_wc,
+                                                   errors);
     }
   }
 
