Index: uspace/app/tester/print/print1.c
===================================================================
--- uspace/app/tester/print/print1.c	(revision 1db6dfd43e6af5b83407ca9b1ef75a0eaddfc7f4)
+++ uspace/app/tester/print/print1.c	(revision f76696fc11f7c97060cb5bfd3e184a0f78e483f9)
@@ -42,5 +42,5 @@
 	
 	TPRINTF("Testing printf(\"%%8.10s\", \"text\"):\n");
-	TPRINTF("Expected output: \"text\"\n");
+	TPRINTF("Expected output: \"    text\"\n");
 	TPRINTF("Real output:     \"%8.10s\"\n\n", "text");
 	
@@ -49,4 +49,8 @@
 	TPRINTF("Real output:     \"%8.10s\"\n\n", "very long text");
 	
+	TPRINTF("Testing printf(\"%%-*.*s\", 7, 7, \"text\"):\n");
+	TPRINTF("Expected output: \"text   \"\n");
+	TPRINTF("Real output:     \"%-*.*s\"\n\n", 7, 7, "text");
+	
 	return NULL;
 }
Index: uspace/app/top/screen.c
===================================================================
--- uspace/app/top/screen.c	(revision 1db6dfd43e6af5b83407ca9b1ef75a0eaddfc7f4)
+++ uspace/app/top/screen.c	(revision f76696fc11f7c97060cb5bfd3e184a0f78e483f9)
@@ -37,4 +37,5 @@
 
 #include <stdio.h>
+#include <stdlib.h>
 #include <io/console.h>
 #include <io/style.h>
@@ -43,4 +44,5 @@
 #include <stats.h>
 #include <inttypes.h>
+#include <macros.h>
 #include "screen.h"
 #include "top.h"
@@ -48,10 +50,13 @@
 #define USEC_COUNT  1000000
 
-static sysarg_t warn_col = 0;
-static sysarg_t warn_row = 0;
 static suseconds_t timeleft = 0;
 
 console_ctrl_t *console;
 
+static sysarg_t warning_col = 0;
+static sysarg_t warning_row = 0;
+static suseconds_t warning_timeleft = 0;
+static char *warning_text = NULL;
+
 static void screen_style_normal(void)
 {
@@ -64,4 +69,10 @@
 	console_flush(console);
 	console_set_style(console, STYLE_INVERTED);
+}
+
+static void screen_style_emphasis(void)
+{
+	console_flush(console);
+	console_set_style(console, STYLE_EMPHASIS);
 }
 
@@ -126,4 +137,7 @@
 void screen_done(void)
 {
+	free(warning_text);
+	warning_text = NULL;
+
 	screen_restart(true);
 	
@@ -277,18 +291,51 @@
 }
 
-static inline void print_tasks_head(void)
+static inline void print_help_head(void)
 {
 	screen_style_inverted();
-	printf("[taskid] [thrds] [resident] [%%resi] [virtual] [%%virt]"
-	    " [%%user] [%%kern] [name");
+	printf("Help");
 	screen_newline();
 	screen_style_normal();
 }
 
-static inline void print_tasks(data_t *data)
-{
-	sysarg_t cols;
-	sysarg_t rows;
-	screen_get_size(&cols, &rows);
+static inline void print_help(void)
+{
+	sysarg_t cols;
+	sysarg_t rows;
+	screen_get_size(&cols, &rows);
+	
+	screen_newline();
+	
+	printf("Operation modes:");
+	screen_newline();
+	
+	printf(" t .. tasks statistics");
+	screen_newline();
+	
+	printf(" i .. IPC statistics");
+	screen_newline();
+	
+	printf(" e .. exceptions statistics");
+	screen_newline();
+	
+	printf("      a .. toggle display of all/hot exceptions");
+	screen_newline();
+
+	printf(" h .. toggle this help screen");
+	screen_newline();
+
+	screen_newline();
+
+	printf("Other keys:");
+	screen_newline();
+	
+	printf(" s .. choose column to sort by");
+	screen_newline();
+	
+	printf(" r .. toggle reversed sorting");
+	screen_newline();
+	
+	printf(" q .. quit");
+	screen_newline();
 	
 	sysarg_t col;
@@ -296,32 +343,4 @@
 	screen_get_pos(&col, &row);
 	
-	size_t i;
-	for (i = 0; (i < data->tasks_count) && (row < rows); i++, row++) {
-		stats_task_t *task = data->tasks + data->tasks_map[i];
-		perc_task_t *perc = data->tasks_perc + data->tasks_map[i];
-		
-		uint64_t resmem;
-		const char *resmem_suffix;
-		bin_order_suffix(task->resmem, &resmem, &resmem_suffix, true);
-		
-		uint64_t virtmem;
-		const char *virtmem_suffix;
-		bin_order_suffix(task->virtmem, &virtmem, &virtmem_suffix, true);
-		
-		printf("%-8" PRIu64 " %7zu %7" PRIu64 "%s ",
-		    task->task_id, task->threads, resmem, resmem_suffix);
-		print_percent(perc->resmem, 2);
-		printf(" %6" PRIu64 "%s ", virtmem, virtmem_suffix);
-		print_percent(perc->virtmem, 2);
-		puts(" ");
-		print_percent(perc->ucycles, 2);
-		puts(" ");
-		print_percent(perc->kcycles, 2);
-		puts(" ");
-		print_string(task->name);
-		
-		screen_newline();
-	}
-	
 	while (row < rows) {
 		screen_newline();
@@ -330,14 +349,30 @@
 }
 
-static inline void print_ipc_head(void)
-{
+static inline void print_table_head(const table_t *table)
+{
+	sysarg_t cols;
+	sysarg_t rows;
+	screen_get_size(&cols, &rows);
+
 	screen_style_inverted();
-	printf("[taskid] [cls snt] [cls rcv] [ans snt]"
-	    " [ans rcv] [irq rcv] [forward] [name");
+	for (size_t i = 0; i < table->num_columns; i++) {
+		const char *name = table->columns[i].name;
+		int width = table->columns[i].width;
+		if (i != 0) {
+			puts(" ");
+		}
+		if (width == 0) {
+			sysarg_t col;
+			sysarg_t row;
+			screen_get_pos(&col, &row);
+			width = cols - col - 1;
+		}
+		printf("[%-*.*s]", width - 2, width - 2, name);
+	}
 	screen_newline();
 	screen_style_normal();
 }
 
-static inline void print_ipc(data_t *data)
+static inline void print_table(const table_t *table)
 {
 	sysarg_t cols;
@@ -350,44 +385,58 @@
 	
 	size_t i;
-	for (i = 0; (i < data->tasks_count) && (row < rows); i++, row++) {
-		uint64_t call_sent;
-		uint64_t call_received;
-		uint64_t answer_sent;
-		uint64_t answer_received;
-		uint64_t irq_notif_received;
-		uint64_t forwarded;
-		
-		char call_sent_suffix;
-		char call_received_suffix;
-		char answer_sent_suffix;
-		char answer_received_suffix;
-		char irq_notif_received_suffix;
-		char forwarded_suffix;
-		
-		order_suffix(data->tasks[i].ipc_info.call_sent, &call_sent,
-		    &call_sent_suffix);
-		order_suffix(data->tasks[i].ipc_info.call_received,
-		    &call_received, &call_received_suffix);
-		order_suffix(data->tasks[i].ipc_info.answer_sent,
-		    &answer_sent, &answer_sent_suffix);
-		order_suffix(data->tasks[i].ipc_info.answer_received,
-		    &answer_received, &answer_received_suffix);
-		order_suffix(data->tasks[i].ipc_info.irq_notif_received,
-		    &irq_notif_received, &irq_notif_received_suffix);
-		order_suffix(data->tasks[i].ipc_info.forwarded, &forwarded,
-		    &forwarded_suffix);
-		
-		printf("%-8" PRIu64 " %8" PRIu64 "%c %8" PRIu64 "%c"
-		     " %8" PRIu64 "%c %8" PRIu64 "%c %8" PRIu64 "%c"
-		     " %8" PRIu64 "%c ", data->tasks[i].task_id,
-		     call_sent, call_sent_suffix,
-		     call_received, call_received_suffix,
-		     answer_sent, answer_sent_suffix,
-		     answer_received, answer_received_suffix,
-		     irq_notif_received, irq_notif_received_suffix,
-		     forwarded, forwarded_suffix);
-		print_string(data->tasks[i].name);
-		
-		screen_newline();
+	for (i = 0; (i < table->num_fields) && (row < rows); i++) {
+		size_t column_index = i % table->num_columns;
+		int width = table->columns[column_index].width;
+		field_t *field = &table->fields[i];
+
+		if (column_index != 0) {
+			puts(" ");
+		}
+
+		if (width == 0) {
+			screen_get_pos(&col, &row);
+			width = cols - col - 1;
+		}
+
+		switch (field->type) {
+			case FIELD_EMPTY:
+				printf("%*s", width, "");
+				break;
+			case FIELD_UINT:
+				printf("%*" PRIu64, width, field->uint);
+				break;
+			case FIELD_UINT_SUFFIX_BIN: {
+				uint64_t val = field->uint;
+				const char *suffix;
+				width -= 3;
+				bin_order_suffix(val, &val, &suffix, true);
+				printf("%*" PRIu64 "%s", width, val, suffix);
+				break;
+				}
+			case FIELD_UINT_SUFFIX_DEC: {
+				uint64_t val = field->uint;
+				char suffix;
+				width -= 1;
+				order_suffix(val, &val, &suffix);
+				printf("%*" PRIu64 "%c", width, val, suffix);
+				break;
+				}
+			case FIELD_PERCENT:
+				width -= 5; /* nnn.% */
+				if (width > 2) {
+					printf("%*s", width - 2, "");
+					width = 2;
+				}
+				print_percent(field->fixed, width);
+				break;
+			case FIELD_STRING:
+				printf("%-*.*s", width, width, field->string);
+				break;
+		}
+
+		if (column_index == table->num_columns - 1) {
+			screen_newline();
+			row++;
+		}
 	}
 	
@@ -398,13 +447,5 @@
 }
 
-static inline void print_excs_head(void)
-{
-	screen_style_inverted();
-	printf("[exc   ] [count   ] [%%count] [cycles  ] [%%cycles] [description");
-	screen_newline();
-	screen_style_normal();
-}
-
-static inline void print_excs(data_t *data)
+static inline void print_sort(table_t *table)
 {
 	sysarg_t cols;
@@ -415,28 +456,8 @@
 	sysarg_t row;
 	screen_get_pos(&col, &row);
-	
-	size_t i;
-	for (i = 0; (i < data->exceptions_count) && (row < rows); i++) {
-		/* Filter-out cold exceptions if not instructed otherwise */
-		if ((!excs_all) && (!data->exceptions[i].hot))
-			continue;
-		
-		uint64_t count;
-		uint64_t cycles;
-		
-		char count_suffix;
-		char cycles_suffix;
-		
-		order_suffix(data->exceptions[i].count, &count, &count_suffix);
-		order_suffix(data->exceptions[i].cycles, &cycles, &cycles_suffix);
-		
-		printf("%-8u %9" PRIu64 "%c  ",
-		     data->exceptions[i].id, count, count_suffix);
-		print_percent(data->exceptions_perc[i].count, 2);
-		printf(" %9" PRIu64 "%c   ", cycles, cycles_suffix);
-		print_percent(data->exceptions_perc[i].cycles, 2);
-		puts(" ");
-		print_string(data->exceptions[i].desc);
-		
+
+	size_t num = min(table->num_columns, rows - row);
+	for (size_t i = 0; i < num; i++) {
+		printf("%c - %s", table->columns[i].key, table->columns[i].name);
 		screen_newline();
 		row++;
@@ -449,37 +470,16 @@
 }
 
-static void print_help(void)
-{
-	sysarg_t cols;
-	sysarg_t rows;
-	screen_get_size(&cols, &rows);
-	
-	sysarg_t col;
-	sysarg_t row;
-	screen_get_pos(&col, &row);
-	
-	screen_newline();
-	
-	printf("Operation modes:");
-	screen_newline();
-	
-	printf(" t .. tasks statistics");
-	screen_newline();
-	
-	printf(" i .. IPC statistics");
-	screen_newline();
-	
-	printf(" e .. exceptions statistics");
-	screen_newline();
-	
-	printf("      a .. toggle display of all/hot exceptions");
-	screen_newline();
-	
-	row += 6;
-	
-	while (row < rows) {
-		screen_newline();
-		row++;
-	}
+static inline void print_warning(void)
+{
+	screen_get_pos(&warning_col, &warning_row);
+	if (warning_timeleft > 0) {
+		screen_style_emphasis();
+		print_string(warning_text);
+		screen_style_normal();
+	} else {
+		free(warning_text);
+		warning_text = NULL;
+	}
+	screen_newline();
 }
 
@@ -492,24 +492,16 @@
 	print_cpu_info(data);
 	print_physmem_info(data);
-	
-	/* Empty row for warnings */
-	screen_get_pos(&warn_col, &warn_row);
-	screen_newline();
-	
-	switch (op_mode) {
-	case OP_TASKS:
-		print_tasks_head();
-		print_tasks(data);
+	print_warning();
+	
+	switch (screen_mode) {
+	case SCREEN_TABLE:
+		print_table_head(&data->table);
+		print_table(&data->table);
 		break;
-	case OP_IPC:
-		print_ipc_head();
-		print_ipc(data);
+	case SCREEN_SORT:
+		print_sort(&data->table);
 		break;
-	case OP_EXCS:
-		print_excs_head();
-		print_excs(data);
-		break;
-	case OP_HELP:
-		print_tasks_head();
+	case SCREEN_HELP:
+		print_help_head();
 		print_help();
 	}
@@ -518,14 +510,25 @@
 }
 
-void print_warning(const char *fmt, ...)
-{
-	screen_moveto(warn_col, warn_row);
-	
+void show_warning(const char *fmt, ...)
+{
+	sysarg_t cols;
+	sysarg_t rows;
+	screen_get_size(&cols, &rows);
+
+	size_t warning_text_size = 1 + cols * sizeof(*warning_text);
+	free(warning_text);
+	warning_text = malloc(warning_text_size);
+	if (!warning_text)
+		return;
+
 	va_list args;
 	va_start(args, fmt);
-	vprintf(fmt, args);
+	vsnprintf(warning_text, warning_text_size, fmt, args);
 	va_end(args);
 	
-	screen_newline();
+	warning_timeleft = 2 * USEC_COUNT;
+
+	screen_moveto(warning_col, warning_row);
+	print_warning();
 	console_flush(console);
 }
@@ -555,8 +558,10 @@
 		kbd_event_t event;
 		
+		warning_timeleft -= timeleft;
 		if (!console_get_kbd_event_timeout(console, &event, &timeleft)) {
 			timeleft = 0;
 			return -1;
 		}
+		warning_timeleft += timeleft;
 		
 		if (event.type == KEY_PRESS)
Index: uspace/app/top/screen.h
===================================================================
--- uspace/app/top/screen.h	(revision 1db6dfd43e6af5b83407ca9b1ef75a0eaddfc7f4)
+++ uspace/app/top/screen.h	(revision f76696fc11f7c97060cb5bfd3e184a0f78e483f9)
@@ -43,5 +43,5 @@
 extern void screen_done(void);
 extern void print_data(data_t *);
-extern void print_warning(const char *, ...);
+extern void show_warning(const char *, ...);
 
 extern int tgetchar(unsigned int);
Index: uspace/app/top/top.c
===================================================================
--- uspace/app/top/top.c	(revision 1db6dfd43e6af5b83407ca9b1ef75a0eaddfc7f4)
+++ uspace/app/top/top.c	(revision f76696fc11f7c97060cb5bfd3e184a0f78e483f9)
@@ -55,7 +55,80 @@
 #define MINUTE  60
 
-op_mode_t op_mode = OP_TASKS;
-sort_mode_t sort_mode = SORT_TASK_CYCLES;
-bool excs_all = false;
+typedef enum {
+	OP_TASKS,
+	OP_IPC,
+	OP_EXCS,
+} op_mode_t;
+
+static const column_t task_columns[] = {
+	{"taskid",   't',  8},
+	{"thrds",    'h',  7},
+	{"resident", 'r', 10},
+	{"%resi",    'R',  7},
+	{"virtual",  'v',  9},
+	{"%virt",    'V',  7},
+	{"%user",    'U',  7},
+	{"%kern",    'K',  7},
+	{"name",     'd',  0},
+};
+
+enum {
+	TASK_COL_ID = 0,
+	TASK_COL_NUM_THREADS,
+	TASK_COL_RESIDENT,
+	TASK_COL_PERCENT_RESIDENT,
+	TASK_COL_VIRTUAL,
+	TASK_COL_PERCENT_VIRTUAL,
+	TASK_COL_PERCENT_USER,
+	TASK_COL_PERCENT_KERNEL,
+	TASK_COL_NAME,
+	TASK_NUM_COLUMNS,
+};
+
+static const column_t ipc_columns[] = {
+	{"taskid",  't', 8},
+	{"cls snt", 'c', 9},
+	{"cls rcv", 'C', 9},
+	{"ans snt", 'a', 9},
+	{"ans rcv", 'A', 9},
+	{"forward", 'f', 9},
+	{"name",    'd', 0},
+};
+
+enum {
+	IPC_COL_TASKID = 0,
+	IPC_COL_CLS_SNT,
+	IPC_COL_CLS_RCV,
+	IPC_COL_ANS_SNT,
+	IPC_COL_ANS_RCV,
+	IPC_COL_FORWARD,
+	IPC_COL_NAME,
+	IPC_NUM_COLUMNS,
+};
+
+static const column_t exception_columns[] = {
+	{"exc",         'e',  8},
+	{"count",       'n', 10},
+	{"%count",      'N',  8},
+	{"cycles",      'c', 10},
+	{"%cycles",     'C',  9},
+	{"description", 'd',  0},
+};
+
+enum {
+	EXCEPTION_COL_ID = 0,
+	EXCEPTION_COL_COUNT,
+	EXCEPTION_COL_PERCENT_COUNT,
+	EXCEPTION_COL_CYCLES,
+	EXCEPTION_COL_PERCENT_CYCLES,
+	EXCEPTION_COL_DESCRIPTION,
+	EXCEPTION_NUM_COLUMNS,
+};
+
+screen_mode_t screen_mode = SCREEN_TABLE;
+static op_mode_t op_mode = OP_TASKS;
+static size_t sort_column = TASK_COL_PERCENT_USER;
+static int sort_reverse = -1;
+static bool excs_all = false;
 
 static const char *read_data(data_t *target)
@@ -67,5 +140,4 @@
 	target->tasks = NULL;
 	target->tasks_perc = NULL;
-	target->tasks_map = NULL;
 	target->threads = NULL;
 	target->exceptions = NULL;
@@ -76,4 +148,9 @@
 	target->ecycles_diff = NULL;
 	target->ecount_diff = NULL;
+	target->table.name = NULL;
+	target->table.num_columns = 0;
+	target->table.columns = NULL;
+	target->table.num_fields = 0;
+	target->table.fields = NULL;
 	
 	/* Get current time */
@@ -117,9 +194,4 @@
 	if (target->tasks_perc == NULL)
 		return "Not enough memory for task utilization";
-	
-	target->tasks_map =
-	    (size_t *) calloc(target->tasks_count, sizeof(size_t));
-	if (target->tasks_map == NULL)
-		return "Not enough memory for task map";
 	
 	/* Get threads */
@@ -289,29 +361,172 @@
 static int cmp_data(void *a, void *b, void *arg)
 {
-	size_t ia = *((size_t *) a);
-	size_t ib = *((size_t *) b);
-	data_t *data = (data_t *) arg;
-	
-	uint64_t acycles = data->ucycles_diff[ia] + data->kcycles_diff[ia];
-	uint64_t bcycles = data->ucycles_diff[ib] + data->kcycles_diff[ib];
-	
-	if (acycles > bcycles)
-		return -1;
-	
-	if (acycles < bcycles)
-		return 1;
-	
+	field_t *fa = (field_t *)a + sort_column;
+	field_t *fb = (field_t *)b + sort_column;
+	
+	if (fa->type > fb->type)
+		return 1 * sort_reverse;
+
+	if (fa->type < fb->type)
+		return -1 * sort_reverse;
+
+	switch (fa->type) {
+		case FIELD_EMPTY:
+			return 0;
+		case FIELD_UINT_SUFFIX_BIN: /* fallthrough */
+		case FIELD_UINT_SUFFIX_DEC: /* fallthrough */
+		case FIELD_UINT:
+			if (fa->uint > fb->uint)
+				return 1 * sort_reverse;
+			if (fa->uint < fb->uint)
+				return -1 * sort_reverse;
+			return 0;
+		case FIELD_PERCENT:
+			if (fa->fixed.upper * fb->fixed.lower
+			    > fb->fixed.upper * fa->fixed.lower)
+				return 1 * sort_reverse;
+			if (fa->fixed.upper * fb->fixed.lower
+			    < fb->fixed.upper * fa->fixed.lower)
+				return -1 * sort_reverse;
+			return 0;
+		case FIELD_STRING:
+			return str_cmp(fa->string, fb->string) * sort_reverse;
+	}
+
 	return 0;
 }
 
-static void sort_data(data_t *data)
-{
-	size_t i;
-	
-	for (i = 0; i < data->tasks_count; i++)
-		data->tasks_map[i] = i;
-	
-	qsort((void *) data->tasks_map, data->tasks_count,
-	    sizeof(size_t), cmp_data, (void *) data);
+static void sort_table(table_t *table)
+{
+	if (sort_column >= table->num_columns)
+		sort_column = 0;
+	/* stable sort is probably best, so we use gsort */
+	gsort((void *) table->fields, table->num_fields / table->num_columns,
+	    sizeof(field_t) * table->num_columns, cmp_data, NULL);
+}
+
+static const char *fill_task_table(data_t *data)
+{
+	data->table.name = "Tasks";
+	data->table.num_columns = TASK_NUM_COLUMNS;
+	data->table.columns = task_columns;
+	data->table.num_fields = data->tasks_count * TASK_NUM_COLUMNS;
+	data->table.fields = calloc(data->table.num_fields,
+	    sizeof(field_t));
+	if (data->table.fields == NULL)
+		return "Not enough memory for table fields";
+
+	field_t *field = data->table.fields;
+	for (size_t i = 0; i < data->tasks_count; i++) {
+		stats_task_t *task = &data->tasks[i];
+		perc_task_t *perc = &data->tasks_perc[i];
+		field[TASK_COL_ID].type = FIELD_UINT;
+		field[TASK_COL_ID].uint = task->task_id;
+		field[TASK_COL_NUM_THREADS].type = FIELD_UINT;
+		field[TASK_COL_NUM_THREADS].uint = task->threads;
+		field[TASK_COL_RESIDENT].type = FIELD_UINT_SUFFIX_BIN;
+		field[TASK_COL_RESIDENT].uint = task->resmem;
+		field[TASK_COL_PERCENT_RESIDENT].type = FIELD_PERCENT;
+		field[TASK_COL_PERCENT_RESIDENT].fixed = perc->resmem;
+		field[TASK_COL_VIRTUAL].type = FIELD_UINT_SUFFIX_BIN;
+		field[TASK_COL_VIRTUAL].uint = task->virtmem;
+		field[TASK_COL_PERCENT_VIRTUAL].type = FIELD_PERCENT;
+		field[TASK_COL_PERCENT_VIRTUAL].fixed = perc->virtmem;
+		field[TASK_COL_PERCENT_USER].type = FIELD_PERCENT;
+		field[TASK_COL_PERCENT_USER].fixed = perc->ucycles;
+		field[TASK_COL_PERCENT_KERNEL].type = FIELD_PERCENT;
+		field[TASK_COL_PERCENT_KERNEL].fixed = perc->kcycles;
+		field[TASK_COL_NAME].type = FIELD_STRING;
+		field[TASK_COL_NAME].string = task->name;
+		field += TASK_NUM_COLUMNS;
+	}
+
+	return NULL;
+}
+
+static const char *fill_ipc_table(data_t *data)
+{
+	data->table.name = "IPC";
+	data->table.num_columns = IPC_NUM_COLUMNS;
+	data->table.columns = ipc_columns;
+	data->table.num_fields = data->tasks_count * IPC_NUM_COLUMNS;
+	data->table.fields = calloc(data->table.num_fields,
+	    sizeof(field_t));
+	if (data->table.fields == NULL)
+		return "Not enough memory for table fields";
+
+	field_t *field = data->table.fields;
+	for (size_t i = 0; i < data->tasks_count; i++) {
+		field[IPC_COL_TASKID].type = FIELD_UINT;
+		field[IPC_COL_TASKID].uint = data->tasks[i].task_id;
+		field[IPC_COL_CLS_SNT].type = FIELD_UINT_SUFFIX_DEC;
+		field[IPC_COL_CLS_SNT].uint = data->tasks[i].ipc_info.call_sent;
+		field[IPC_COL_CLS_RCV].type = FIELD_UINT_SUFFIX_DEC;
+		field[IPC_COL_CLS_RCV].uint = data->tasks[i].ipc_info.call_received;
+		field[IPC_COL_ANS_SNT].type = FIELD_UINT_SUFFIX_DEC;
+		field[IPC_COL_ANS_SNT].uint = data->tasks[i].ipc_info.answer_sent;
+		field[IPC_COL_ANS_RCV].type = FIELD_UINT_SUFFIX_DEC;
+		field[IPC_COL_ANS_RCV].uint = data->tasks[i].ipc_info.answer_received;
+		field[IPC_COL_FORWARD].type = FIELD_UINT_SUFFIX_DEC;
+		field[IPC_COL_FORWARD].uint = data->tasks[i].ipc_info.forwarded;
+		field[IPC_COL_NAME].type = FIELD_STRING;
+		field[IPC_COL_NAME].string = data->tasks[i].name;
+		field += IPC_NUM_COLUMNS;
+	}
+
+	return NULL;
+}
+
+static const char *fill_exception_table(data_t *data)
+{
+	data->table.name = "Exceptions";
+	data->table.num_columns = EXCEPTION_NUM_COLUMNS;
+	data->table.columns = exception_columns;
+	data->table.num_fields = data->exceptions_count *
+	    EXCEPTION_NUM_COLUMNS;
+	data->table.fields = calloc(data->table.num_fields, sizeof(field_t));
+	if (data->table.fields == NULL)
+		return "Not enough memory for table fields";
+
+	field_t *field = data->table.fields;
+	for (size_t i = 0; i < data->exceptions_count; i++) {
+		if (!excs_all && !data->exceptions[i].hot)
+			continue;
+		field[EXCEPTION_COL_ID].type = FIELD_UINT;
+		field[EXCEPTION_COL_ID].uint = data->exceptions[i].id;
+		field[EXCEPTION_COL_COUNT].type = FIELD_UINT_SUFFIX_DEC;
+		field[EXCEPTION_COL_COUNT].uint = data->exceptions[i].count;
+		field[EXCEPTION_COL_PERCENT_COUNT].type = FIELD_PERCENT;
+		field[EXCEPTION_COL_PERCENT_COUNT].fixed = data->exceptions_perc[i].count;
+		field[EXCEPTION_COL_CYCLES].type = FIELD_UINT_SUFFIX_DEC;
+		field[EXCEPTION_COL_CYCLES].uint = data->exceptions[i].cycles;
+		field[EXCEPTION_COL_PERCENT_CYCLES].type = FIELD_PERCENT;
+		field[EXCEPTION_COL_PERCENT_CYCLES].fixed = data->exceptions_perc[i].cycles;
+		field[EXCEPTION_COL_DESCRIPTION].type = FIELD_STRING;
+		field[EXCEPTION_COL_DESCRIPTION].string = data->exceptions[i].desc;
+		field += EXCEPTION_NUM_COLUMNS;
+	}
+
+	/* in case any cold exceptions were ignored */
+	data->table.num_fields = field - data->table.fields;
+
+	return NULL;
+}
+
+static const char *fill_table(data_t *data)
+{
+	if (data->table.fields != NULL) {
+		free(data->table.fields);
+		data->table.fields = NULL;
+	}
+
+	switch (op_mode) {
+		case OP_TASKS:
+			return fill_task_table(data);
+		case OP_IPC:
+			return fill_ipc_table(data);
+		case OP_EXCS:
+			return fill_exception_table(data);
+	}
+	return NULL;
 }
 
@@ -356,4 +571,7 @@
 	if (target->ecount_diff != NULL)
 		free(target->ecount_diff);
+
+	if (target->table.fields != NULL)
+		free(target->table.fields);
 }
 
@@ -367,44 +585,66 @@
 	printf("Reading initial data...\n");
 	
-	if ((ret = read_data(&data_prev)) != NULL)
+	if ((ret = read_data(&data)) != NULL)
 		goto out;
 	
 	/* Compute some rubbish to have initialised values */
-	compute_percentages(&data_prev, &data_prev);
+	compute_percentages(&data, &data);
 	
 	/* And paint screen until death */
 	while (true) {
 		int c = tgetchar(UPDATE_INTERVAL);
-		if (c < 0) {
+
+		if (c < 0) { /* timeout */
+			data_prev = data;
 			if ((ret = read_data(&data)) != NULL) {
-				free_data(&data);
+				free_data(&data_prev);
 				goto out;
 			}
 			
 			compute_percentages(&data_prev, &data);
-			sort_data(&data);
-			print_data(&data);
 			free_data(&data_prev);
-			data_prev = data;
-			
-			continue;
-		}
-		
+
+			c = -1;
+		}
+
+		if (screen_mode == SCREEN_HELP && c >= 0) {
+			if (c == 'h' || c == '?')
+				c = -1;
+			/* go back to table and handle the key */
+			screen_mode = SCREEN_TABLE;
+		}
+
+		if (screen_mode == SCREEN_SORT && c >= 0) {
+			for (size_t i = 0; i < data.table.num_columns; i++) {
+				if (data.table.columns[i].key == c) {
+					sort_column = i;
+					screen_mode = SCREEN_TABLE;
+				}
+			}
+
+			c = -1;
+		}
+
 		switch (c) {
+			case -1: /* do nothing */
+				break;
 			case 't':
-				print_warning("Showing task statistics");
 				op_mode = OP_TASKS;
 				break;
 			case 'i':
-				print_warning("Showing IPC statistics");
 				op_mode = OP_IPC;
 				break;
 			case 'e':
-				print_warning("Showing exception statistics");
 				op_mode = OP_EXCS;
 				break;
+			case 's':
+				screen_mode = SCREEN_SORT;
+				break;
+			case 'r':
+				sort_reverse = -sort_reverse;
+				break;
 			case 'h':
-				print_warning("Showing help");
-				op_mode = OP_HELP;
+			case '?':
+				screen_mode = SCREEN_HELP;
 				break;
 			case 'q':
@@ -414,18 +654,25 @@
 					excs_all = !excs_all;
 					if (excs_all)
-						print_warning("Showing all exceptions");
+						show_warning("Showing all exceptions");
 					else
-						print_warning("Showing only hot exceptions");
+						show_warning("Showing only hot exceptions");
 					break;
 				}
+				/* fallthrough */
 			default:
-				print_warning("Unknown command \"%c\", use \"h\" for help", c);
-				break;
-		}
+				show_warning("Unknown command \"%c\", use \"h\" for help", c);
+				continue; /* don't redraw */
+		}
+
+		if ((ret = fill_table(&data)) != NULL) {
+			goto out;
+		}
+		sort_table(&data.table);
+		print_data(&data);
 	}
 	
 out:
 	screen_done();
-	free_data(&data_prev);
+	free_data(&data);
 	
 	if (ret != NULL) {
Index: uspace/app/top/top.h
===================================================================
--- uspace/app/top/top.h	(revision 1db6dfd43e6af5b83407ca9b1ef75a0eaddfc7f4)
+++ uspace/app/top/top.h	(revision f76696fc11f7c97060cb5bfd3e184a0f78e483f9)
@@ -51,17 +51,10 @@
 
 typedef enum {
-	OP_TASKS,
-	OP_IPC,
-	OP_EXCS,
-	OP_HELP
-} op_mode_t;
+	SCREEN_TABLE,
+	SCREEN_SORT,
+	SCREEN_HELP,
+} screen_mode_t;
 
-typedef enum {
-	SORT_TASK_CYCLES
-} sort_mode_t;
-
-extern op_mode_t op_mode;
-extern sort_mode_t sort_mode;
-extern bool excs_all;
+extern screen_mode_t screen_mode;
 
 typedef struct {
@@ -87,4 +80,32 @@
 } perc_exc_t;
 
+typedef enum {
+	FIELD_EMPTY, FIELD_UINT, FIELD_UINT_SUFFIX_BIN, FIELD_UINT_SUFFIX_DEC,
+	FIELD_PERCENT, FIELD_STRING
+} field_type_t;
+
+typedef struct {
+	field_type_t type;
+	union {
+		fixed_float fixed;
+		uint64_t uint;
+		const char *string;
+	};
+} field_t;
+
+typedef struct {
+	const char *name;
+	char key;
+	int width;
+} column_t;
+
+typedef struct {
+	const char *name;
+	size_t num_columns;
+	const column_t *columns;
+	size_t num_fields;
+	field_t *fields;
+} table_t;
+
 typedef struct {
 	time_t hours;
@@ -107,5 +128,4 @@
 	stats_task_t *tasks;
 	perc_task_t *tasks_perc;
-	size_t *tasks_map;
 	
 	size_t threads_count;
@@ -122,4 +142,6 @@
 	uint64_t *ecycles_diff;
 	uint64_t *ecount_diff;
+
+	table_t table;
 } data_t;
 
Index: uspace/lib/c/generic/io/printf_core.c
===================================================================
--- uspace/lib/c/generic/io/printf_core.c	(revision 1db6dfd43e6af5b83407ca9b1ef75a0eaddfc7f4)
+++ uspace/lib/c/generic/io/printf_core.c	(revision f76696fc11f7c97060cb5bfd3e184a0f78e483f9)
@@ -283,5 +283,5 @@
 	/* Print leading spaces. */
 	size_t strw = str_length(str);
-	if (precision == 0)
+	if (precision == 0 || precision > strw)
 		precision = strw;
 	
@@ -331,5 +331,5 @@
 	/* Print leading spaces. */
 	size_t strw = wstr_length(str);
-	if (precision == 0)
+	if (precision == 0 || precision > strw)
 		precision = strw;
 	
