diff --git a/include/utils.h b/include/utils.h
index 9a81494d..e91c5f0e 100644
--- a/include/utils.h
+++ b/include/utils.h
@@ -372,6 +372,8 @@ const char *str_map_lookup_u8(const struct str_num_map *map, uint8_t val);
 unsigned int get_str_char_count(const char *str, int match);
 int str_split_by_char(char *str, char **before, char **after, int match);
 
+int escape_str (char *dst, const char *src, int bufsize);
+
 #define INDENT_STR_MAXLEN 32
 
 struct indent_mem {
diff --git a/lib/Makefile b/lib/Makefile
index aa7bbd2e..c259722f 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -5,7 +5,7 @@ CFLAGS += -fPIC
 
 UTILOBJ = utils.o utils_math.o rt_names.o ll_map.o ll_types.o ll_proto.o ll_addr.o \
 	inet_proto.o namespace.o json_writer.o json_print.o json_print_math.o \
-	names.o color.o bpf_legacy.o bpf_glue.o exec.o fs.o cg_map.o ppp_proto.o
+	names.o color.o bpf_legacy.o bpf_glue.o exec.o fs.o cg_map.o ppp_proto.o escape.o
 
 ifeq ($(HAVE_ELF),y)
 ifeq ($(HAVE_LIBBPF),y)
diff --git a/lib/escape.c b/lib/escape.c
new file mode 100644
index 00000000..b110f61b
--- /dev/null
+++ b/lib/escape.c
@@ -0,0 +1,111 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Escape character print handling derived from procps
+ * Copyright 1998-2002 by Albert Cahalan
+ * Copyright 2020-2022 Jim Warner <james.warner@comcast.net>
+ *
+ */
+
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <langinfo.h>
+
+#include "utils.h"
+
+static const char UTF_tab[] = {
+	1,  1,	1,  1,	1,  1,	1,  1,
+	1,  1,	1,  1,	1,  1,	1,  1, // 0x00 - 0x0F
+	1,  1,	1,  1,	1,  1,	1,  1,
+	1,  1,	1,  1,	1,  1,	1,  1, // 0x10 - 0x1F
+	1,  1,	1,  1,	1,  1,	1,  1,
+	1,  1,	1,  1,	1,  1,	1,  1, // 0x20 - 0x2F
+	1,  1,	1,  1,	1,  1,	1,  1,
+	1,  1,	1,  1,	1,  1,	1,  1, // 0x30 - 0x3F
+	1,  1,	1,  1,	1,  1,	1,  1,
+	1,  1,	1,  1,	1,  1,	1,  1, // 0x40 - 0x4F
+	1,  1,	1,  1,	1,  1,	1,  1,
+	1,  1,	1,  1,	1,  1,	1,  1, // 0x50 - 0x5F
+	1,  1,	1,  1,	1,  1,	1,  1,
+	1,  1,	1,  1,	1,  1,	1,  1, // 0x60 - 0x6F
+	1,  1,	1,  1,	1,  1,	1,  1,
+	1,  1,	1,  1,	1,  1,	1,  1, // 0x70 - 0x7F
+	-1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1, -1, -1, -1, -1, -1, -1, // 0x80 - 0x8F
+	-1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1, -1, -1, -1, -1, -1, -1, // 0x90 - 0x9F
+	-1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1, -1, -1, -1, -1, -1, -1, // 0xA0 - 0xAF
+	-1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1, -1, -1, -1, -1, -1, -1, // 0xB0 - 0xBF
+	-1, -1, 2,  2,	2,  2,	2,  2,
+	2,  2,	2,  2,	2,  2,	2,  2, // 0xC0 - 0xCF
+	2,  2,	2,  2,	2,  2,	2,  2,
+	2,  2,	2,  2,	2,  2,	2,  2, // 0xD0 - 0xDF
+	3,  3,	3,  3,	3,  3,	3,  3,
+	3,  3,	3,  3,	3,  3,	3,  3, // 0xE0 - 0xEF
+	4,  4,	4,  4,	4,  -1, -1, -1,
+	-1, -1, -1, -1, -1, -1, -1, -1, // 0xF0 - 0xFF
+};
+
+static const unsigned char ESC_tab[] = {
+	"@..............................." // 0x00 - 0x1F
+	"||||||||||||||||||||||||||||||||" // 0x20 - 0x3F
+	"||||||||||||||||||||||||||||||||" // 0x40 - 0x5f
+	"|||||||||||||||||||||||||||||||." // 0x60 - 0x7F
+	"????????????????????????????????" // 0x80 - 0x9F
+	"????????????????????????????????" // 0xA0 - 0xBF
+	"????????????????????????????????" // 0xC0 - 0xDF
+	"????????????????????????????????" // 0xE0 - 0xFF
+};
+
+static void esc_all(unsigned char *str)
+{
+	// if bad locale/corrupt str, replace non-printing stuff
+	while (*str) {
+		unsigned char c = ESC_tab[*str];
+
+		if (c != '|')
+			*str = c;
+		++str;
+	}
+}
+
+static void esc_ctl(unsigned char *str, int len)
+{
+	int i;
+
+	for (i = 0; i < len;) {
+		// even with a proper locale, strings might be corrupt
+		int n = UTF_tab[*str];
+
+		if (n < 0 || i + n > len) {
+			esc_all(str);
+			return;
+		}
+		// and eliminate those non-printing control characters
+		if (*str < 0x20 || *str == 0x7f)
+			*str = '?';
+		str += n;
+		i += n;
+	}
+}
+
+int escape_str(char *dst, const char *src, int bufsize)
+{
+	static int utf_sw;
+
+	if (utf_sw == 0) {
+		char *enc = nl_langinfo(CODESET);
+
+		utf_sw = enc && strcasecmp(enc, "UTF-8") == 0 ? 1 : -1;
+	}
+
+	int n = strlcpy(dst, src, bufsize);
+
+	if (utf_sw < 0)
+		esc_all((unsigned char *)dst);
+	else
+		esc_ctl((unsigned char *)dst, n);
+	return n;
+}
diff --git a/misc/ss.c b/misc/ss.c
index aef1a714..1d70242e 100644
--- a/misc/ss.c
+++ b/misc/ss.c
@@ -550,8 +550,7 @@ static void user_ent_add(unsigned int ino, char *task,
 static void user_ent_hash_build_task(char *path, int pid, int tid)
 {
 	const char *no_ctx = "unavailable";
-	char task[16] = {'\0', };
-	char stat[MAX_PATH_LEN];
+	char task[20] = { };
 	int pos_id, pos_fd;
 	char *task_context;
 	struct dirent *d;
@@ -599,6 +598,8 @@ static void user_ent_hash_build_task(char *path, int pid, int tid)
 			sock_context = strdup(no_ctx);
 
 		if (task[0] == '\0') {
+			char stat[MAX_PATH_LEN];
+			char name[16];
 			FILE *fp;
 
 			strlcpy(stat, path, pos_id + 1);
@@ -606,9 +607,8 @@ static void user_ent_hash_build_task(char *path, int pid, int tid)
 
 			fp = fopen(stat, "r");
 			if (fp) {
-				if (fscanf(fp, "%*d (%[^)])", task) < 1) {
-					; /* ignore */
-				}
+				if (fscanf(fp, "%*d (%[^)])", name) == 1)
+					escape_str(task, name, sizeof(task));
 				fclose(fp);
 			}
 		}
