Subject: Re: ddb help command patch
To: Martin Husemann <martin@duskware.de>
From: Adam Hamsik <haaaad@gmail.com>
List: tech-kern
Date: 06/26/2007 17:48:32
--Apple-Mail-20-554765863
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
charset=US-ASCII;
delsp=yes;
format=flowed
On Jun 8, 2007, at 12:56 AM, Martin Husemann wrote:
> On Fri, Jun 08, 2007 at 12:19:59AM +0200, Adam Hamsik wrote:
>> db>help break
>>
>> prints "Set breakpoint to address"
>
> I like the general idea, but I would like to make the additional help
> support optional. This has two reasons:
>
> - ddb is very usefull even in tiny configurations where every kernel
> byte counts
> - people doing heavy kernel work might decide to not need the help
> a lot (or have the other machine, where they cross build from,
> easily available to run "man ddb")
>
> Could you define a macro to declare the DDB command and make it
> just ignore
> the help string parameter if compiled w/o the DDB_VERBOSE_HELP (or
> whatever
> we'd call it) option?
>
> i.e. change this
>
> { "mbuf", "Show number and status of mbuf's.",
> db_mbuf_print_cmd,
> 0, NULL },
>
> to:
>
> DDB_DECLARE_SIMPLE_CMD("mbuf", "Show number and status of mbuf's.",
> db_mbuf_print_cmd)
>
> (and a non-SIMPLE variant if the "flags" or "more" field are filled
> in?)
>
>
> And feel free to use other names, I'm not good at inventing them ;-}
>
> Martin
Hi
I have written another patch which improve my last patch little bit.
I have added to entries to
struct db_command {
char *cmd_arg;
char *cmd_descr;
};
and I defined new macro DDB_ADD_CMD. DDB_ADD_CMD if option
DDB_VERBOSE_HELP is defined adds description and argument message. If
DDB_VERBOSE_HELP is not defined DDB_ADD_CMD will add only NULL for
them (for every entry 2 x char*).
In this structure I store all help infos. I have declared kernel
option DDB_VERBOSE_HELP ,too.
Before I fully implement this patch I want to ask some questions.
I have to convert all other architectures for now only i386 is
converted.
1) ddb(4) is incomplete in our man page we don't have any info about
callout
show aio_jobs
show lock
show malloc
show mbuf
Can somebody send me info's about these functions and I will add
them to our
ddb(4) and to the my patch.
2) I think about dynamic ddb command/help API which could be used for
registering/unregistering ddb commands. Now all ddb commands are
stored in static array db_command_table. Problem is that if every
kernel component want to add some ddb_commands kernel programer have
to add entry to the ddb_command_table. If we use dynamic structure
like linked list we can add/remove commands in realtime.
API:
int
db_reg_command(struct cmd_list *cmd_list, struct cmd_list **cmd_command,
char *cmd_name,*cmd_func);
db_reg_help(*cmd_command,char* descr, char** cmd_func_arg);
db_unreg_command(*cmd_list, char* cmd_name);
struct cmd_list*
db_search_command(struct cmd_list*, char* cmd_name);
/*this function will create default list of commands*/
db_init(struct cmd_list**);
(db_get_help,db_get_func,db_get_cmd) ??
Pro:
LKM and other parts of kernel can register/unregister ddb commands in
easy way.
more useable ddb.
Cons:
I have to initialize this list when ddb is run.(lot of malloc's for
every added command 2 one for command structure and second for help
(not needed))
Regards
-----------------------------------------
Adam Hamsik
jabber: haad@jabber.org
icq: 249727910
Proud NetBSD user.
We program to have fun.
Even when we program for money, we want to have fun as well.
~ Yukihiro Matsumoto
--Apple-Mail-20-554765863
Content-Transfer-Encoding: 7bit
Content-Type: application/octet-stream;
x-unix-mode=0644;
name=ddb_help_2.diff
Content-Disposition: attachment;
filename=ddb_help_2.diff
Index: db_command.c
===================================================================
RCS file: /cvsroot/src/sys/ddb/db_command.c,v
retrieving revision 1.96
diff -u -r1.96 db_command.c
--- db_command.c 30 Apr 2007 14:44:30 -0000 1.96
+++ db_command.c 26 Jun 2007 15:46:58 -0000
@@ -83,7 +83,23 @@
#define CMD_FOUND 1
#define CMD_NONE 2
#define CMD_AMBIGUOUS 3
-#define CMD_HELP 4
+
+#define CMD_SWITCH(result) do { \
+ switch (result) { \
+ case CMD_NONE: \
+ db_printf("No such command\n"); \
+ db_flush_lex(); \
+ return; \
+ case CMD_AMBIGUOUS: \
+ db_printf("Ambiguous\n"); \
+ db_flush_lex(); \
+ return; \
+ default: \
+ break; \
+ } \
+ } while(0)
+
+
/*
* Exported global variables
@@ -103,6 +119,7 @@
static bool db_ed_style = true;
static void db_buf_print_cmd(db_expr_t, bool, db_expr_t, const char *);
+
static void db_cmd_list(const struct db_command *);
static int db_cmd_search(const char *, const struct db_command *,
const struct db_command **);
@@ -110,6 +127,10 @@
const struct db_command *);
static void db_event_print_cmd(db_expr_t, bool, db_expr_t, const char *);
static void db_fncall(db_expr_t, bool, db_expr_t, const char *);
+static void db_help_print_cmd(db_expr_t, bool, db_expr_t, const char *);
+static void db_lock_print_cmd(db_expr_t, bool, db_expr_t, const char *);
+static void db_mount_print_cmd(db_expr_t, bool, db_expr_t, const char *);
+static void db_mbuf_print_cmd(db_expr_t, bool, db_expr_t, const char *);
static void db_malloc_print_cmd(db_expr_t, bool, db_expr_t, const char *);
static void db_map_print_cmd(db_expr_t, bool, db_expr_t, const char *);
static void db_namecache_print_cmd(db_expr_t, bool, db_expr_t, const char *);
@@ -123,46 +144,64 @@
static void db_sync_cmd(db_expr_t, bool, db_expr_t, const char *);
static void db_uvmexp_print_cmd(db_expr_t, bool, db_expr_t, const char *);
static void db_vnode_print_cmd(db_expr_t, bool, db_expr_t, const char *);
-static void db_lock_print_cmd(db_expr_t, bool, db_expr_t, const char *);
-static void db_mount_print_cmd(db_expr_t, bool, db_expr_t, const char *);
-static void db_mbuf_print_cmd(db_expr_t, bool, db_expr_t, const char *);
+
/*
* 'show' commands
*/
-
+#if defined(DDB_VERBOSE_HELP)
+#define DDB_ADD_CMD(name,funct,type,cmd_descr,cmd_arg,more)\
+ name,funct,type,cmd_descr,cmd_arg,more
+#else
+#define DDB_ADD_CMD(name,funct,type,cmd_descr,cmd_arg,more)\
+ name,funct,type,NULL,NULL,more
+#endif
+
static const struct db_command db_show_all_cmds[] = {
- { "callout", db_show_callout, 0, NULL },
- { "pages", db_show_all_pages, 0, NULL },
- { "procs", db_show_all_procs, 0, NULL },
- { "pools", db_show_all_pools, 0, NULL },
- { NULL, NULL, 0, NULL }
+ { DDB_ADD_CMD("callout",db_show_callout, 0,NULL,NULL,NULL) },
+ { DDB_ADD_CMD("pages", db_show_all_pages, 0,NULL,NULL,NULL) },
+ { DDB_ADD_CMD("procs", db_show_all_procs, 0,NULL,NULL,NULL) },
+ { DDB_ADD_CMD("pools", db_show_all_pools, 0,NULL,NULL,NULL) },
+ { DDB_ADD_CMD(NULL, NULL, 0, NULL,NULL,NULL )}
};
static const struct db_command db_show_cmds[] = {
- { "aio_jobs", db_show_aio_jobs, 0, NULL },
- { "all", NULL, 0, db_show_all_cmds },
+ { DDB_ADD_CMD("aio_jobs", db_show_aio_jobs, 0,"Show aio jobs", NULL, NULL) },
+ { DDB_ADD_CMD("all", NULL, 0,NULL,NULL, db_show_all_cmds )},
#if defined(INET) && (NARP > 0)
- { "arptab", db_show_arptab, 0, NULL },
+ { DDB_ADD_CMD("arptab", db_show_arptab, 0,NULL,NULL, NULL) },
#endif
- { "breaks", db_listbreak_cmd, 0, NULL },
- { "buf", db_buf_print_cmd, 0, NULL },
- { "event", db_event_print_cmd, 0, NULL },
- { "lock", db_lock_print_cmd, 0, NULL },
- { "malloc", db_malloc_print_cmd, 0, NULL },
- { "map", db_map_print_cmd, 0, NULL },
- { "mount", db_mount_print_cmd, 0, NULL },
- { "mbuf", db_mbuf_print_cmd, 0, NULL },
- { "ncache", db_namecache_print_cmd, 0, NULL },
- { "object", db_object_print_cmd, 0, NULL },
- { "page", db_page_print_cmd, 0, NULL },
- { "pool", db_pool_print_cmd, 0, NULL },
- { "registers", db_show_regs, 0, NULL },
- { "sched_qs", db_show_sched_qs, 0, NULL },
- { "uvmexp", db_uvmexp_print_cmd, 0, NULL },
- { "vnode", db_vnode_print_cmd, 0, NULL },
- { "watches", db_listwatch_cmd, 0, NULL },
- { NULL, NULL, 0, NULL }
+ { DDB_ADD_CMD("breaks", db_listbreak_cmd, 0,"Display all breaks.", NULL, NULL) },
+ { DDB_ADD_CMD("buf", db_buf_print_cmd, 0,
+ "Print the struct buf at address.", "[/f] address", NULL) },
+ { DDB_ADD_CMD("event", db_event_print_cmd, 0,
+ "Print all the non-zero evcnt(9) event counters.", "[/f]", NULL) },
+ { DDB_ADD_CMD("lock", db_lock_print_cmd, 0,NULL,NULL,NULL) },
+ { DDB_ADD_CMD("malloc", db_malloc_print_cmd,0,NULL,NULL,NULL) },
+ { DDB_ADD_CMD("map", db_map_print_cmd, 0,
+ "Print the vm_map at address.", "[/f] address", NULL) },
+ { DDB_ADD_CMD("mount", db_mount_print_cmd, 0,
+ "Print the mount structure at address.", "[/f] address", NULL) },
+ { DDB_ADD_CMD("mbuf", db_mbuf_print_cmd, 0,NULL,NULL, NULL) },
+ { DDB_ADD_CMD("ncache", db_namecache_print_cmd, 0,
+ "Dump the namecache list.", "address", NULL) },
+ { DDB_ADD_CMD("object", db_object_print_cmd, 0,
+ "Print the vm_object at address.", "[/f] address", NULL) },
+ { DDB_ADD_CMD("page", db_page_print_cmd, 0,
+ "Print the vm_page at address.", "[/f] address", NULL) },
+ { DDB_ADD_CMD("pool", db_pool_print_cmd, 0,
+ "Print the pool at address.", "[/clp] address", NULL) },
+ { DDB_ADD_CMD("registers", db_show_regs, 0,
+ "Display the register set.", "[/u]", NULL) },
+ { DDB_ADD_CMD("sched_qs", db_show_sched_qs, 0,
+ "Print the state of the scheduler's run queues.",NULL, NULL) },
+ { DDB_ADD_CMD("uvmexp", db_uvmexp_print_cmd, 0,
+ "Print a selection of UVM counters and statistics.", NULL, NULL) },
+ { DDB_ADD_CMD("vnode", db_vnode_print_cmd, 0,
+ "Print the vnode at address.", "[/f] address", NULL) },
+ { DDB_ADD_CMD("watches", db_listwatch_cmd, 0,
+ "Display all watchpoints.", NULL, NULL) },
+ { DDB_ADD_CMD(NULL, NULL, 0,NULL,NULL, NULL )}
};
/* arch/<arch>/<arch>/db_interface.c */
@@ -171,45 +210,82 @@
#endif
static const struct db_command db_command_table[] = {
- { "b", db_breakpoint_cmd, 0, NULL },
- { "break", db_breakpoint_cmd, 0, NULL },
- { "bt", db_stack_trace_cmd, 0, NULL },
- { "c", db_continue_cmd, 0, NULL },
- { "call", db_fncall, CS_OWN, NULL },
- { "callout", db_show_callout, 0, NULL },
- { "continue", db_continue_cmd, 0, NULL },
- { "d", db_delete_cmd, 0, NULL },
- { "delete", db_delete_cmd, 0, NULL },
- { "dmesg", db_dmesg, 0, NULL },
- { "dwatch", db_deletewatch_cmd, 0, NULL },
- { "examine", db_examine_cmd, CS_SET_DOT, NULL },
- { "kill", db_kill_proc, CS_OWN, NULL },
+ { DDB_ADD_CMD("b", db_breakpoint_cmd, 0,
+ "Set a breakpoint at address", "[/u] address[,count].", NULL) },
+ { DDB_ADD_CMD("break", db_breakpoint_cmd, 0,
+ "Set a breakpoint at address", "[/u] address[,count].", NULL) },
+ { DDB_ADD_CMD("bt", db_stack_trace_cmd, 0,
+ "Show backtrace.", "See help trace.", NULL) },
+ { DDB_ADD_CMD("c", db_continue_cmd, 0,"Continue execution.", "[/c]", NULL) },
+ { DDB_ADD_CMD("call", db_fncall, CS_OWN,
+ "Call the function", "address[(expression[,...])]", NULL) },
+ { DDB_ADD_CMD("callout", db_show_callout, 0, NULL, NULL, NULL) },
+ { DDB_ADD_CMD("continue", db_continue_cmd, 0,"Continue execution.", "[/c]", NULL) },
+ { DDB_ADD_CMD("d", db_delete_cmd, 0,
+ "Delete a breakpoint.", "address | #number", NULL) },
+ { DDB_ADD_CMD("delete", db_delete_cmd, 0,
+ "Delete a breakpoint.", "address | #number", NULL) },
+ { DDB_ADD_CMD("dmesg", db_dmesg, 0,
+ "Show kernel message buffer.", "[count]", NULL) },
+ { DDB_ADD_CMD("dwatch", db_deletewatch_cmd, 0,
+ "Delete the watchpoint.", "address", NULL) },
+ { DDB_ADD_CMD("examine", db_examine_cmd, CS_SET_DOT,
+ "Display the address locations.",
+ "[/modifier] address[,count]", NULL) },
+ { DDB_ADD_CMD("help", db_help_print_cmd, CS_OWN,"Display help about commands",
+ "Use other commands as arguments.", NULL) },
+ { DDB_ADD_CMD("kill", db_kill_proc, CS_OWN,
+ "Send a signal to the process","pid[,signal_number]", NULL) },
#ifdef KGDB
- { "kgdb", db_kgdb_cmd, 0, NULL },
+ { DDB_ADD_CMD("kgdb", db_kgdb_cmd, 0, NULL) },
#endif
#ifdef DB_MACHINE_COMMANDS
- { "machine", NULL, 0, db_machine_command_table },
+ { DDB_ADD_CMD("machine", NULL, 0, NULL,NULL,db_machine_command_table) },
#endif
- { "match", db_trace_until_matching_cmd,0, NULL },
- { "next", db_trace_until_matching_cmd,0, NULL },
- { "p", db_print_cmd, 0, NULL },
- { "print", db_print_cmd, 0, NULL },
- { "ps", db_show_all_procs, 0, NULL },
- { "reboot", db_reboot_cmd, CS_OWN, NULL },
- { "s", db_single_step_cmd, 0, NULL },
- { "search", db_search_cmd, CS_OWN|CS_SET_DOT, NULL },
- { "set", db_set_cmd, CS_OWN, NULL },
- { "show", NULL, 0, db_show_cmds },
- { "sifting", db_sifting_cmd, CS_OWN, NULL },
- { "step", db_single_step_cmd, 0, NULL },
- { "sync", db_sync_cmd, CS_OWN, NULL },
- { "trace", db_stack_trace_cmd, 0, NULL },
- { "until", db_trace_until_call_cmd,0, NULL },
- { "w", db_write_cmd, CS_MORE|CS_SET_DOT, NULL },
- { "watch", db_watchpoint_cmd, CS_MORE, NULL },
- { "write", db_write_cmd, CS_MORE|CS_SET_DOT, NULL },
- { "x", db_examine_cmd, CS_SET_DOT, NULL },
- { NULL, NULL, 0, NULL }
+ { DDB_ADD_CMD("match", db_trace_until_matching_cmd,0,
+ "Stop at the matching return instruction.","See help next", NULL) },
+ { DDB_ADD_CMD("next", db_trace_until_matching_cmd,0,
+ "Stop at the matching return instruction.","[/p]", NULL) },
+ { DDB_ADD_CMD("p", db_print_cmd, 0,
+ "Print address according to the format.",
+ "[/axzodurc] address [address ...]", NULL) },
+ { DDB_ADD_CMD("print", db_print_cmd, 0,
+ "Print address according to the format.",
+ "[/axzodurc] address [address ...]", NULL) },
+ { DDB_ADD_CMD("ps", db_show_all_procs, 0,
+ "Print all processes.","See show all procs", NULL) },
+ { DDB_ADD_CMD("reboot", db_reboot_cmd, CS_OWN,
+ "Reboot","0x1 RB_ASKNAME, 0x2 RB_SINGLE, 0x4 RB_NOSYNC, 0x8 RB_HALT,"
+ "0x40 RB_KDB, 0x100 RB_DUMP, 0x808 RB_POWERDOWN",NULL) },
+ { DDB_ADD_CMD("s", db_single_step_cmd, 0,
+ "Single-step count times.","[/p] [,count]", NULL) },
+ { DDB_ADD_CMD("search", db_search_cmd, CS_OWN|CS_SET_DOT,
+ "Search memory from address for value.",
+ "[/bhl] address value [mask] [,count]", NULL) },
+ { DDB_ADD_CMD("set", db_set_cmd, CS_OWN,
+ "Set the named variable","$variable [=] expression", NULL) },
+ { DDB_ADD_CMD("show", NULL, 0,"Show kernel stats.", NULL, db_show_cmds)},
+ { DDB_ADD_CMD("sifting", db_sifting_cmd, CS_OWN,
+ "Search the symbol tables ","[/F] string", NULL) },
+ { DDB_ADD_CMD("step", db_single_step_cmd, 0,
+ "Single-step count times.","[/p] [,count]", NULL) },
+ { DDB_ADD_CMD("sync", db_sync_cmd, CS_OWN,
+ "Force a crash dump, and then reboot.",NULL, NULL) },
+ { DDB_ADD_CMD("trace", db_stack_trace_cmd, 0,
+ "Stack trace from frame-address.",
+ "[/u[l]] [frame-address][,count]", NULL) },
+ { DDB_ADD_CMD("until", db_trace_until_call_cmd,0,
+ "Stop at the next call or return instruction.","[/p]", NULL) },
+ { DDB_ADD_CMD("w", db_write_cmd, CS_MORE|CS_SET_DOT,
+ "Set a watchpoint for a region. ","address[,size]", NULL) },
+ { DDB_ADD_CMD("watch", db_watchpoint_cmd, CS_MORE,
+ "Set a watchpoint for a region. ","address[,size]", NULL) },
+ { DDB_ADD_CMD("write", db_write_cmd, CS_MORE|CS_SET_DOT,
+ "Write the expressions at succeeding locations.",
+ "[/bhl] address expression [expression ...]", NULL) },
+ { DDB_ADD_CMD("x", db_examine_cmd, CS_SET_DOT,
+ "Display the address locations.", "[/modifier] address[,count]", NULL) },
+ { DDB_ADD_CMD(NULL, NULL, 0, NULL, NULL, NULL )}
};
static const struct db_command *db_last_command = NULL;
@@ -220,6 +296,9 @@
#endif /* defined(DDB_COMMANDONENTER) */
#define DB_LINE_SEP ';'
+
+
+
/*
* Utility routine - discard tokens through end-of-line.
*/
@@ -227,7 +306,6 @@
db_skip_to_eol(void)
{
int t;
-
do {
t = db_read_token();
} while (t != tEOL);
@@ -342,18 +420,12 @@
}
}
}
- if (result == CMD_NONE) {
- /* check for 'help' */
- if (name[0] == 'h' && name[1] == 'e'
- && name[2] == 'l' && name[3] == 'p')
- result = CMD_HELP;
- }
return (result);
}
static void
-db_cmd_list(const struct db_command *table)
-{
+db_cmd_list(const struct db_command *table){
+
int i, j, w, columns, lines, width=0, numcmds;
const char *p;
@@ -434,29 +506,14 @@
*/
while (cmd_table) {
result = db_cmd_search(db_tok_string, cmd_table, &cmd);
- switch (result) {
- case CMD_NONE:
- db_printf("No such command\n");
- db_flush_lex();
- return;
- case CMD_AMBIGUOUS:
- db_printf("Ambiguous\n");
- db_flush_lex();
- return;
- case CMD_HELP:
- db_cmd_list(cmd_table);
- db_flush_lex();
- return;
- default:
- break;
- }
+ CMD_SWITCH(result);
if ((cmd_table = cmd->more) != 0) {
t = db_read_token();
- if (t != tIDENT) {
+/* if (t != tIDENT) {
db_cmd_list(cmd_table);
db_flush_lex();
return;
- }
+ }*/
}
}
@@ -529,6 +586,55 @@
}
}
}
+static void
+db_help_print_cmd(db_expr_t addr, bool have_addr, db_expr_t count,
+ const char *modif){
+
+ const struct db_command *help;
+
+ int t,result;
+
+ t=db_read_token();
+ if (t == tIDENT){
+ result = db_cmd_search(db_tok_string, db_command_table, &help);
+
+ CMD_SWITCH(result);
+
+ if (help->cmd_descr != NULL)
+ db_printf("Command: %s Arguments: %s\n Descr: %s\n",
+ help->name,help->cmd_arg,help->cmd_descr);
+ else
+ db_printf("Command: %s Doesn't have any help message included.\n",
+ help->name);
+
+ while (help->more != NULL){
+
+ t=db_read_token();
+
+ if (t == tIDENT){
+ result = db_cmd_search(db_tok_string, help->more, &help);
+
+ CMD_SWITCH(result);
+
+ if (help->cmd_arg != NULL)
+ db_printf("Command: %s Arguments: %s\n Descr: %s\n",
+ help->name,help->cmd_arg,help->cmd_descr);
+ else
+ db_printf("Command: %s Doesn't have any help message included.\n",
+ help->name);
+ } else {
+ db_printf("%s subcommands:\n",help->name);
+ db_cmd_list(help->more);
+ break;
+ }
+ }
+ } else {
+ db_cmd_list(db_command_table);
+ db_flush_lex();
+ }
+
+ return;
+}
/*ARGSUSED*/
static void
Index: db_command.h
===================================================================
RCS file: /cvsroot/src/sys/ddb/db_command.h,v
retrieving revision 1.27
diff -u -r1.27 db_command.h
--- db_command.h 22 Feb 2007 04:38:05 -0000 1.27
+++ db_command.h 26 Jun 2007 15:46:58 -0000
@@ -52,12 +52,17 @@
*/
struct db_command {
const char *name; /* command name */
- /* function to call */
+
+ /* function to call */
void (*fcn)(db_expr_t, bool, db_expr_t, const char *);
int flag; /* extra info: */
#define CS_OWN 0x1 /* non-standard syntax */
#define CS_MORE 0x2 /* standard syntax, but may have other
words at end */
#define CS_SET_DOT 0x100 /* set dot after command */
- const struct db_command *more; /* another level of command */
+
+ const char *cmd_descr; /*description of command*/
+ const char *cmd_arg; /*command arguments*/
+
+ const struct db_command *more; /* another level of command */
};
Index: files.ddb
===================================================================
RCS file: /cvsroot/src/sys/ddb/files.ddb,v
retrieving revision 1.1
diff -u -r1.1 files.ddb
--- files.ddb 27 Nov 2005 22:44:35 -0000 1.1
+++ files.ddb 26 Jun 2007 15:46:58 -0000
@@ -3,10 +3,10 @@
#
# DDB options
#
-defflag opt_ddb.h DDB
+defflag opt_ddb.h DDB
defparam opt_ddbparam.h DDB_FROMCONSOLE DDB_ONPANIC DDB_HISTORY_SIZE
DDB_BREAK_CHAR DDB_KEYCODE SYMTAB_SPACE
- DDB_COMMANDONENTER
+ DDB_COMMANDONENTER DDB_VERBOSE_HELP
file ddb/db_access.c ddb | kgdb # XXX kgdb reference
file ddb/db_aout.c ddb
--Apple-Mail-20-554765863
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
charset=US-ASCII;
format=flowed
--Apple-Mail-20-554765863--