Runing programs can spit core if they crashed. It will be picked up

by gdb and debug it with nice dialog.
Processes are now kept in list so pid/cmd can be easily tracked.
Rest...
This commit is contained in:
Sanel Zukan 2007-08-06 12:39:15 +00:00
parent 817e0cd918
commit a80b5dcb2a
11 changed files with 1066 additions and 13 deletions

123
evoke/Crash.cpp Normal file
View File

@ -0,0 +1,123 @@
/*
* $Id$
*
* Evoke, head honcho of everything
* Part of Equinox Desktop Environment (EDE).
* Copyright (c) 2000-2007 EDE Authors.
*
* This program is licensed under terms of the
* GNU General Public License version 2 or newer.
* See COPYING for details.
*/
#include "icons/core.xpm"
#include "Crash.h"
#include "Spawn.h"
#include <FL/Fl.h>
#include <FL/Fl_Pixmap.h>
#include <edelib/Nls.h>
#include <edelib/File.h>
#include <stdio.h> // snprintf
#define DIALOG_W 380
#define DIALOG_H 130
#define DIALOG_W_EXPANDED 380
#define DIALOG_H_EXPANDED 340
void show_details_cb(Fl_Widget*, void* cd) {
CrashDialog* c = (CrashDialog*)cd;
c->show_details();
}
void close_cb(Fl_Widget*, void* cd) {
CrashDialog* c = (CrashDialog*)cd;
c->hide();
}
void dummy_timeout(void*) { }
CrashDialog::CrashDialog() : Fl_Window(DIALOG_W, DIALOG_H, _("World is going down...")) {
trace_loaded = 0;
begin();
pix = new Fl_Pixmap(core_xpm);
icon_box = new Fl_Box(10, 10, 70, 75);
icon_box->image(pix);
txt_box = new Fl_Box(85, 10, 285, 75);
txt_box->align(FL_ALIGN_WRAP | FL_ALIGN_LEFT | FL_ALIGN_INSIDE);
close = new Fl_Button(280, 95, 90, 25, _("&Close"));
close->callback(close_cb, this);
details = new Fl_Button(10, 95, 265, 25, _("@> Show backtrace"));
details->box(FL_FLAT_BOX);
details->align(FL_ALIGN_INSIDE | FL_ALIGN_LEFT);
details->callback(show_details_cb, this);
// widgets for expanded dialog
trace_log = new Fl_Text_Display(10, 130, 360, 165);
trace_buff = new Fl_Text_Buffer();
trace_log->buffer(trace_buff);
trace_log->hide();
save_as = new Fl_Button(280, 305, 90, 25, _("&Save As..."));
save_as->hide();
copy = new Fl_Button(185, 305, 90, 25, _("&Copy"));
copy->hide();
end();
}
CrashDialog::~CrashDialog() {
// looks like fltk does not clean image() assigned data
delete pix;
}
void CrashDialog::show_details(void) {
if(trace_log->visible()) {
trace_log->hide();
save_as->hide();
copy->hide();
details->label(_("@> Show backtrace"));
size(DIALOG_W, DIALOG_H);
} else {
trace_log->show();
save_as->show();
copy->show();
details->label(_("@< Hide backtrace"));
size(DIALOG_W_EXPANDED, DIALOG_H_EXPANDED);
if(!trace_loaded) {
trace_buff->remove(0, trace_buff->length());
// read core
spawn_backtrace(cmd.c_str(), "core", "/tmp/gdb_output", "/tmp/gdb_script");
trace_buff->appendfile("/tmp/gdb_output");
trace_loaded = 1;
// delete core
edelib::file_remove("core");
}
}
}
void CrashDialog::set_data(const char* command) {
cmd = command;
char txt[1024];
snprintf(txt, sizeof(txt), _("Program just crashed !!!\n\nYou can inspect details about this crash by clicking on 'Show backtrace' below"));
txt_box->copy_label(txt);
trace_loaded = 0;
}
void CrashDialog::run(void) {
if(!shown()) {
set_modal();
show();
}
while(shown())
Fl::wait();
}

50
evoke/Crash.h Normal file
View File

@ -0,0 +1,50 @@
/*
* $Id$
*
* Evoke, head honcho of everything
* Part of Equinox Desktop Environment (EDE).
* Copyright (c) 2000-2007 EDE Authors.
*
* This program is licensed under terms of the
* GNU General Public License version 2 or newer.
* See COPYING for details.
*/
#ifndef __CRASH_H__
#define __CRASH_H__
#include <FL/Fl_Window.h>
#include <FL/Fl_Box.h>
#include <FL/Fl_Button.h>
#include <FL/Fl_Pixmap.h>
#include <FL/Fl_Text_Display.h>
#include <FL/Fl_Text_Buffer.h>
#include <edelib/String.h>
class CrashDialog : public Fl_Window {
private:
Fl_Pixmap* pix;
Fl_Box* txt_box;
Fl_Box* icon_box;
Fl_Button* close;
Fl_Button* details;
Fl_Text_Display* trace_log;
Fl_Text_Buffer* trace_buff;
Fl_Button* save_as;
Fl_Button* copy;
edelib::String cmd;
bool trace_loaded;
public:
CrashDialog();
~CrashDialog();
void show_details(void);
void set_data(const char* command);
void run(void);
};
#endif

View File

@ -15,6 +15,7 @@
#include "EvokeService.h"
#include "Splash.h"
#include "Spawn.h"
#include "Crash.h"
#include <edelib/File.h>
#include <edelib/Config.h>
@ -84,6 +85,10 @@ int get_string_property_value(Atom at, char* txt, int txt_len) {
return 1;
}
void service_watcher_cb(int pid, int signum) {
EvokeService::instance()->service_watcher(pid, signum);
}
EvokeService::EvokeService() : is_running(0), logfile(NULL), pidfile(NULL), lockfile(NULL) {
top_win = NULL;
}
@ -101,6 +106,8 @@ EvokeService::~EvokeService() {
edelib::file_remove(pidfile);
free(pidfile);
}
processes.clear();
}
EvokeService* EvokeService::instance(void) {
@ -243,6 +250,7 @@ bool EvokeService::init_splash(const char* config, bool no_splash, bool dry_run)
void EvokeService::init_autostart(void) {
edelib::String home = edelib::user_config_dir();
home += "/autostart/";
// TODO
}
void EvokeService::setup_atoms(Display* d) {
@ -315,6 +323,77 @@ void EvokeService::quit_x11(void) {
#endif
}
/*
* Monitor starting service and report if staring
* failed. Also if one of runned services crashed
* attach gdb on it pid and run backtrace.
*/
void EvokeService::service_watcher(int pid, int signum) {
//if(signum == 11) {
if(signum == 139) {
EvokeProcess pc;
if(find_and_unregister_process(pid, pc)) {
printf("%s crashed with core dump\n", pc.cmd.c_str());
CrashDialog cdialog;
cdialog.set_data(pc.cmd.c_str());
cdialog.run();
return;
}
} else if(signum == 32512) {
fl_alert("No such file");
}
unregister_process(pid);
}
void EvokeService::register_process(const char* cmd, pid_t pid) {
EvokeProcess pc;
pc.cmd = cmd;
pc.pid = pid;
printf("registering %s with %i\n", cmd, pid);
processes.push_back(pc);
}
void EvokeService::unregister_process(pid_t pid) {
if(processes.empty())
return;
ProcessListIter it = processes.begin();
ProcessListIter it_end = processes.end();
while(it != it_end) {
if((*it).pid == pid) {
printf("Found %s with pid %i, cleaning...\n", (*it).cmd.c_str(), pid);
processes.erase(it);
return;
}
++it;
}
}
bool EvokeService::find_and_unregister_process(pid_t pid, EvokeProcess& pc) {
if(processes.empty())
return 0;
ProcessListIter it = processes.begin();
ProcessListIter it_end = processes.end();
while(it != it_end) {
if((*it).pid == pid) {
printf("Found %s with pid %i, cleaning...\n", (*it).cmd.c_str(), pid);
pc.cmd = (*it).cmd;
pc.pid = pid;
processes.erase(it);
return 1;
}
++it;
}
return 0;
}
int EvokeService::handle(const XEvent* ev) {
logfile->printf("Got event %i\n", ev->type);
@ -333,16 +412,20 @@ int EvokeService::handle(const XEvent* ev) {
} else if(ev->type == PropertyNotify) {
if(ev->xproperty.atom == _ede_spawn) {
char buff[1024];
if(get_string_property_value(_ede_spawn, buff, sizeof(buff))) {
logfile->printf("Got _EVOKE_SPAWN with %s. Starting client...\n", buff);
int r = spawn_program(buff);
pid_t child;
int r = spawn_program_with_core(buff, service_watcher_cb, &child);
if(r != 0)
fl_alert("Unable to start %s. Got code %i", buff, r);
} else
logfile->printf("Got _EVOKE_SPAWN with malformed data. Ignoring...\n");
else
register_process(buff, child);
} else {
logfile->printf("Got _EVOKE_SPAWN with malformed data. Ignoring...\n");
}
return 1;
}

View File

@ -24,11 +24,20 @@ struct EvokeClient {
edelib::String exec; // program name/path to run
};
typedef edelib::list<EvokeClient> ClientList;
typedef edelib::list<EvokeClient>::iterator ClientListIter;
struct EvokeProcess {
edelib::String cmd;
pid_t pid;
};
typedef edelib::list<EvokeClient> ClientList;
typedef edelib::list<EvokeClient>::iterator ClientListIter;
typedef edelib::list<edelib::String> StringList;
typedef edelib::list<edelib::String>::iterator StringListIter;
typedef edelib::list<EvokeProcess> ProcessList;
typedef edelib::list<EvokeProcess>::iterator ProcessListIter;
class Fl_Double_Window;
class EvokeService {
@ -44,7 +53,8 @@ class EvokeService {
Atom _ede_spawn;
Atom _ede_evoke_quit;
ClientList clients;
ClientList clients;
ProcessList processes;
public:
EvokeService();
@ -68,6 +78,12 @@ class EvokeService {
void register_top(Fl_Double_Window* win) { top_win = win; }
void unregister_top(void) { top_win = NULL; }
void register_process(const char* cmd, pid_t pid);
void unregister_process(pid_t pid);
bool find_and_unregister_process(pid_t pid, EvokeProcess& pc);
void service_watcher(int pid, int signum);
void quit_x11(void);
};

View File

@ -10,9 +10,11 @@
SubDir TOP evoke ;
SOURCE = evoke.cpp EvokeService.cpp Spawn.cpp Splash.cpp Log.cpp Logout.cpp ;
SOURCE = evoke.cpp EvokeService.cpp Spawn.cpp Splash.cpp Log.cpp Logout.cpp Crash.cpp ;
EdeProgram evoke : $(SOURCE) ;
FltkProgramBare test/evoke_test : test/evoke_test.cpp ;
#TranslationStrings locale : $(SOURCE) ;
EdeManual doc/evoke.txt ;
#EdeProgram crash : Crash.cpp ;

View File

@ -18,23 +18,54 @@
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h> // getrlimit, setrlimit
#include <sys/resource.h> //
extern char** environ;
// FIXME: is this safe ??? (or to use somehow sig_atomic_t)
SignalWatch* global_watch = 0;
int spawn_program(const char* cmd) {
void sigchld_handler(int sig) {
int pid, status;
do {
errno = 0;
pid = waitpid(WAIT_ANY, &status, WNOHANG);
if(global_watch != 0)
global_watch(pid, status);
} while(pid <= 0 && errno == EINTR);
}
int spawn_program(const char* cmd, SignalWatch wf, pid_t* child_pid_ret) {
if(!cmd)
return SPAWN_EMPTY;
int nulldev = -1;
int status_ret = 0;
if(wf) {
struct sigaction sa;
sa.sa_handler = sigchld_handler;
sa.sa_flags = SA_NOCLDSTOP;
//sa.sa_flags = SA_RESTART;
sigemptyset(&sa.sa_mask);
sigaction(SIGCHLD, &sa, (struct sigaction*)0);
global_watch = wf;
}
pid_t pid = fork();
if(pid == -1)
return SPAWN_FORK_FAILED;
// run the child
if(pid == 0) {
// this is child
char* argv[4];
argv[0] = "/bin/sh";
argv[1] = "-c";
@ -53,12 +84,268 @@ int spawn_program(const char* cmd) {
close(1); dup(nulldev);
close(2); dup(nulldev);
if(execve(argv[0], argv, environ) == -1) {
close(nulldev);
// should not get here
return SPAWN_EXECVE_FAILED;
}
}
}
if(nulldev != -1)
close(nulldev);
/*
* Record child pid; it is returned by fork(), but since this
* function does not wait until child quits, it will return
* immediately, filling (if) requested child pid
*/
if(child_pid_ret)
*child_pid_ret = pid;
return status_ret;
}
int spawn_program_with_core(const char* cmd, SignalWatch* wf, pid_t* child_pid_ret) {
struct rlimit r;
if(getrlimit(RLIMIT_CORE, &r) == -1)
return -1;
rlim_t old = r.rlim_cur;
r.rlim_cur = RLIM_INFINITY;
// FIXME: add own core limit ?
if(setrlimit(RLIMIT_CORE, &r) == -1)
return -1;
int ret = spawn_program(cmd, wf, child_pid_ret);
r.rlim_cur = old;
if(setrlimit(RLIMIT_CORE, &r) == -1)
return -1;
return ret;
}
int spawn_backtrace(const char* program, const char* core_path, const char* output, const char* script) {
const char* gdb_script = "bt\nquit\n";
const int gdb_script_len = 8;
// file with gdb commands
int sfd = open(script, O_WRONLY | O_TRUNC | O_CREAT, 0770);
if(sfd == -1)
return -1;
write(sfd, gdb_script, gdb_script_len);
close(sfd);
// output file with gdb backtrace
int ofd = open(output, O_WRONLY | O_TRUNC | O_CREAT, 0770);
if(ofd == -1)
return -1;
pid_t pid = fork();
if(pid == -1) {
close(ofd);
return -1;
} else if(pid == 0) {
dup2(ofd, 1);
close(ofd);
char* argv[8];
argv[0] = "gdb";
argv[1] = "--quiet";
argv[2] = "--batch";
argv[3] = "-x";
argv[4] = (char*)script;
argv[5] = (char*)program;
argv[6] = (char*)core_path;
argv[7] = 0;
//printf("%s %s %s %s %s %s %s\n", argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]);
execvp(argv[0], argv);
return -1;
} else {
int status;
if(waitpid(pid, &status, 0) != pid)
return -1;
}
return 0;
}
#if 0
int spawn_backtrace(int crash_pid, const char* output, const char* script) {
const char* gdb_script = "info threads\nthread apply all bt full\nquit\n";
const int gdb_script_len = 43;
//const char* gdb_script = "bt\nquit\n";
//const int gdb_script_len = 8;
pid_t parent_pid, child_pid;
//kill(crash_pid, SIGCONT);
char parent_pid_str[64];
parent_pid = crash_pid;
kill(parent_pid, SIGCONT);
//parent_pid = getpid();
sprintf(parent_pid_str, "%ld", (long)parent_pid);
// file with gdb commands
int sfd = open(script, O_WRONLY | O_TRUNC | O_CREAT, 0770);
if(sfd == -1)
return -1;
write(sfd, gdb_script, gdb_script_len);
close(sfd);
// output file with gdb backtrace
int ofd = open(output, O_WRONLY | O_TRUNC | O_CREAT, 0770);
if(ofd == -1)
return -1;
child_pid = fork();
if(child_pid == -1)
return -1;
else if(child_pid == 0) {
//dup2(ofd, 1);
close(ofd);
char* argv[8];
argv[0] = "gdb";
argv[1] = "--quiet";
argv[2] = "--batch";
argv[3] = "-x";
argv[4] = (char*)script;
argv[5] = "--pid";
argv[6] = parent_pid_str;
argv[7] = 0;
//argv[5] = "--pid";
//argv[6] = parent_pid_str;
//argv[7] = 0;
printf("%s %s %s %s %s %s %s\n", argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]);
execvp(argv[0], argv);
return -1;
} else {
/*
int status;
waitpid(child_pid, &status, 0);
*/
}
//unlink(script);
return 0;
}
#if 0
int spawn_backtrace(const char* output, const char* script){
pid_t pid, parent_pid;
int ifd[2], ofd[2], efd[2];
const char* gdb_script = "info threads\nthread apply all by\nquit\n";
const int gdb_script_len = 38;
puts("xxxx");
// write script for gdb
int fds = open(script, O_WRONLY | O_CREAT | O_TRUNC, 0770);
if(fds == -1)
return -1;
write(fds, gdb_script, gdb_script_len);
close(fds);
puts("yyyyy");
errno = 0;
// open output for gdb trace
int fdo = open(output, O_WRONLY, O_CREAT | O_TRUNC, 0770);
if(fdo == -1) {
printf("can't open fdo(%s): %s\n", output, strerror(errno));
return -1;
}
parent_pid = getppid();
char parent_pid_str[64];
sprintf(parent_pid_str, "%d", parent_pid);
char* argv[8];
argv[0] = "gdb";
argv[1] = "--quiet";
argv[2] = "--batch";
argv[3] = "-x";
argv[4] = (char*)script;
argv[5] = "--pid";
argv[6] = parent_pid_str;
argv[7] = 0;
puts("ayyyy");
if(pipe(ifd) == -1) {
printf("cant open ifd pipe: %s\n", strerror(errno));
}
if(pipe(ofd) == -1) {
printf("cant open ofd pipe: %s\n", strerror(errno));
}
if(pipe(efd) == -1) {
printf("cant open efd pipe: %s\n", strerror(errno));
}
pid = fork();
if(pid == -1) {
close(ifd[0]); close(ifd[1]);
close(ofd[0]); close(ofd[1]);
close(efd[0]); close(efd[1]);
return -1;
} else if(pid == 0) {
// child
printf("gdb pid is %i\n", getpid());
printf("%s %s %s %s %s %s %s\n", argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]);
close(ifd[1]);
fclose(stdin);
dup(ifd[0]);
close(ofd[0]);
fclose(stdout);
dup(ofd[1]);
close(efd[0]);
fclose(stderr);
dup(efd[1]);
execvp(argv[0], (char**)argv);
return -1;
} else {
// parent
fclose(stdin);
close(ifd[0]);
close(ofd[1]);
close(efd[1]);
write(fdo, "The trace:\n", 11);
close(ifd[1]);
close(ofd[0]);
close(efd[0]);
close(fdo);
kill(pid, SIGKILL);
waitpid(pid, NULL, 0);
//_exit(0);
}
return 0;
}
#endif
#endif

View File

@ -13,6 +13,8 @@
#ifndef __SPAWN_H__
#define __SPAWN_H__
#include <unistd.h> // pid_t
/*
* This is little bit modified code from edelib run_program()
* so evoke specific stuff can be added. Also, possible option
@ -27,7 +29,14 @@
#define SPAWN_EXECVE_FAILED 65530 // internal execve failed
#define SPAWN_PTY_FAILED 65529 // TODO
#define SPAWN_USER_CANCELED 65528 // TODO
#define SPAWN_CRASHED 65527 // executable crashed
#define SPAWN_KILLED 65526 // executable crashed
#define SPAWN_NOEXITED 65525
int spawn_program(const char* cmd);
typedef void (SignalWatch)(int pid, int status);
int spawn_program(const char* cmd, SignalWatch* wf = 0, pid_t* child_pid_ret = 0);
int spawn_program_with_core(const char* cmd, SignalWatch* wf = 0, pid_t* child_pid_ret = 0);
int spawn_backtrace(const char* program, const char* core_path, const char* output, const char* script);
#endif

28
evoke/fl/crash.fl Normal file
View File

@ -0,0 +1,28 @@
# data file for the Fltk User Interface Designer (fluid)
version 1.0108
header_name {.h}
code_name {.cxx}
Function {} {open
} {
Fl_Window {} {open
xywh {354 160 380 130} type Double visible
} {
Fl_Box {} {
image {icons/core.xpm} xywh {10 10 70 75} labelsize 14
}
Fl_Box {} {
label {Program foo just crashed !!!
You can inspect details about this crash by clicking on 'Show details' below} selected
xywh {85 10 285 75} align 148
}
Fl_Button {} {
label {&Close}
xywh {280 95 90 25}
}
Fl_Button {} {
label {@> Show details}
xywh {10 95 265 25} box FLAT_BOX align 20
}
}
}

View File

@ -0,0 +1,39 @@
# data file for the Fltk User Interface Designer (fluid)
version 1.0108
header_name {.h}
code_name {.cxx}
Function {} {open selected
} {
Fl_Window {} {open
xywh {354 160 380 340} type Double visible
} {
Fl_Box {} {
image {icons/core.xpm} xywh {10 10 70 75} labelsize 14
}
Fl_Box {} {
label {Program foo just crashed !!!
You can inspect details about this crash by clicking on 'Show details' below}
xywh {85 10 285 75} align 148
}
Fl_Button {} {
label {&Close}
xywh {280 95 90 25}
}
Fl_Button {} {
label {@< Hide details}
xywh {10 95 265 25} box FLAT_BOX align 20
}
Fl_Text_Display {} {
xywh {10 130 360 165} labelsize 14 textsize 14
}
Fl_Button {} {
label {&Save As...}
xywh {280 305 90 25}
}
Fl_Button {} {
label {&Copy}
xywh {185 305 90 25}
}
}
}

416
evoke/icons/core.xpm Normal file
View File

@ -0,0 +1,416 @@
/* XPM */
static char * core_xpm[] = {
"48 48 365 2",
" c None",
". c #FAE601",
"+ c #FCF001",
"@ c #FEF800",
"# c #F9E202",
"$ c #F2BE04",
"% c #F2BD04",
"& c #F5CC03",
"* c #FBE901",
"= c #FBEC01",
"- c #EB9A06",
"; c #EEAA05",
"> c #FEFC00",
", c #F7D506",
"' c #E78808",
") c #E88D06",
"! c #D1B46D",
"~ c #C7AF75",
"{ c #C2994A",
"] c #F1BB04",
"^ c #F5CF03",
"/ c #FCEE01",
"( c #EDD426",
"_ c #DCBC76",
": c #E3C06E",
"< c #CCAB4F",
"[ c #E7C16F",
"} c #ECC36B",
"| c #CCB34C",
"1 c #FCFB04",
"2 c #D1C652",
"3 c #D0B76B",
"4 c #DDBA6C",
"5 c #DAB96E",
"6 c #CFBB40",
"7 c #A7A7A7",
"8 c #A8A8A8",
"9 c #A9A9A9",
"0 c #A4A4A4",
"a c #9F9F9F",
"b c #999999",
"c c #919191",
"d c #A5B4BE",
"e c #E7E7E1",
"f c #C3C2C7",
"g c #B3AB9B",
"h c #B5A679",
"i c #C5AF6A",
"j c #D6B86A",
"k c #CDB16B",
"l c #C9B05B",
"m c #B7A27C",
"n c #EBDC19",
"o c #AFAFAF",
"p c #B8B8B8",
"q c #BBBBBB",
"r c #BCBCBC",
"s c #BABABA",
"t c #B4B4B4",
"u c #888888",
"v c #7B7B7B",
"w c #102032",
"x c #808285",
"y c #FBF5F2",
"z c #D9D0BF",
"A c #C6AD71",
"B c #DFBB69",
"C c #D9B86B",
"D c #C0AC7C",
"E c #BBA785",
"F c #AAAAAA",
"G c #ADADAD",
"H c #D6D6D6",
"I c #EBEBEB",
"J c #FCFCFC",
"K c #FEFEFE",
"L c #FFFFFF",
"M c #FBFBFB",
"N c #F5F5F5",
"O c #E7E7E7",
"P c #D2D2D2",
"Q c #868686",
"R c #636363",
"S c #616161",
"T c #6E6E6E",
"U c #3E3E3D",
"V c #3B3A3A",
"W c #161311",
"X c #65605D",
"Y c #C3BCB7",
"Z c #D8CFB0",
"` c #D1B873",
" . c #DFBD6A",
".. c #C8B37E",
"+. c #AAAEBF",
"@. c #FFFD00",
"#. c #B1B1B1",
"$. c #CACACA",
"%. c #E9E9E9",
"&. c #F0F0F0",
"*. c #E5E5E5",
"=. c #E3E3E3",
"-. c #E1E1E1",
";. c #DFDFDF",
">. c #898989",
",. c #3C3C3C",
"'. c #393939",
"). c #262626",
"!. c #1D1D1E",
"~. c #0F0F0F",
"{. c #5A5857",
"]. c #5A5955",
"^. c #4D4D45",
"/. c #918E7A",
"(. c #D3CBA7",
"_. c #E5DCB3",
":. c #E0DECD",
"<. c #AEAEAE",
"[. c #D3D3D3",
"}. c #FAFAFA",
"|. c #FDFDFD",
"1. c #EFEFEF",
"2. c #EAEAEA",
"3. c #DDDDDD",
"4. c #D7D7D7",
"5. c #D0D0D0",
"6. c #C9C9C9",
"7. c #C1C1C1",
"8. c #BDBDBD",
"9. c #7D7D7D",
"0. c #333333",
"a. c #131313",
"b. c #4F4F4F",
"c. c #484848",
"d. c #5F5F60",
"e. c #393C3A",
"f. c #080C09",
"g. c #1F1F1B",
"h. c #817F74",
"i. c #D8D6C6",
"j. c #F2F2E8",
"k. c #DFE7F0",
"l. c #F9F9F9",
"m. c #F3F3F3",
"n. c #EDEDED",
"o. c #E8E8E8",
"p. c #E4E4E4",
"q. c #DEDEDE",
"r. c #CCCCCC",
"s. c #C3C3C3",
"t. c #B9B9B9",
"u. c #ABABAB",
"v. c #A1A1A1",
"w. c #151515",
"x. c #4B4B4B",
"y. c #6B6B6B",
"z. c #3F3F3F",
"A. c #1B1B1B",
"B. c #121211",
"C. c #050604",
"D. c #000000",
"E. c #0C0707",
"F. c #433F37",
"G. c #7E7F79",
"H. c #F7F7F7",
"I. c #ECECEC",
"J. c #E2E2E2",
"K. c #DCDCDC",
"L. c #B0B0B0",
"M. c #A5A5A5",
"N. c #979797",
"O. c #969696",
"P. c #434343",
"Q. c #191919",
"R. c #010101",
"S. c #060001",
"T. c #0C0203",
"U. c #050002",
"V. c #0B0D14",
"W. c #EEEEEE",
"X. c #DADADA",
"Y. c #C6C6C6",
"Z. c #BEBEBE",
"`. c #B6B6B6",
" + c #9C9C9C",
".+ c #909090",
"++ c #848484",
"@+ c #6C6C6C",
"#+ c #1A1A1A",
"$+ c #060606",
"%+ c #020101",
"&+ c #010403",
"*+ c #020405",
"=+ c #010202",
"-+ c #D8D8D8",
";+ c #CFCFCF",
">+ c #C2C2C2",
",+ c #A2A2A2",
"'+ c #878787",
")+ c #787878",
"!+ c #656565",
"~+ c #252525",
"{+ c #080C0D",
"]+ c #F4F4F4",
"^+ c #D9D9D9",
"/+ c #D4D4D4",
"(+ c #CECECE",
"_+ c #C5C5C5",
":+ c #BFBFBF",
"<+ c #B2B2B2",
"[+ c #A0A0A0",
"}+ c #989898",
"|+ c #8E8E8E",
"1+ c #737373",
"2+ c #6D6D6D",
"3+ c #585858",
"4+ c #202020",
"5+ c #070707",
"6+ c #4B4C4C",
"7+ c #767676",
"8+ c #D1D1D1",
"9+ c #A3A3A3",
"0+ c #9B9B9B",
"a+ c #949494",
"b+ c #8C8C8C",
"c+ c #838383",
"d+ c #797979",
"e+ c #6F6F6F",
"f+ c #676767",
"g+ c #4A4A4A",
"h+ c #030303",
"i+ c #181818",
"j+ c #040404",
"k+ c #565656",
"l+ c #D5D5D5",
"m+ c #C7C7C7",
"n+ c #C4C4C4",
"o+ c #ACACAC",
"p+ c #9E9E9E",
"q+ c #7E7E7E",
"r+ c #666666",
"s+ c #606060",
"t+ c #575757",
"u+ c #353535",
"v+ c #383738",
"w+ c #777677",
"x+ c #494949",
"y+ c #CBCBCB",
"z+ c #B5B5B5",
"A+ c #9A9A9A",
"B+ c #939393",
"C+ c #8B8B8B",
"D+ c #7C7C7C",
"E+ c #5E5E5E",
"F+ c #595959",
"G+ c #424242",
"H+ c #171717",
"I+ c #0C0C0C",
"J+ c #5D5D5D",
"K+ c #2F2F2F",
"L+ c #8F8F8F",
"M+ c #8A8A8A",
"N+ c #858585",
"O+ c #7F7F7F",
"P+ c #757575",
"Q+ c #686868",
"R+ c #5B5B5B",
"S+ c #4D4D4D",
"T+ c #6F6D6E",
"U+ c #3B3B3B",
"V+ c #9D9D9D",
"W+ c #959595",
"X+ c #8D8D8D",
"Y+ c #828282",
"Z+ c #6A6A6A",
"`+ c #646464",
" @ c #555555",
".@ c #2E2E2E",
"+@ c #0B0B0B",
"@@ c #020202",
"#@ c #222222",
"$@ c #1D1D1D",
"%@ c #080808",
"&@ c #808080",
"*@ c #7A7A7A",
"=@ c #707070",
"-@ c #5F5F5F",
";@ c #545454",
">@ c #525252",
",@ c #2B2B2B",
"'@ c #0D0D0D",
")@ c #050505",
"!@ c #111111",
"~@ c #5A5A5A",
"{@ c #929292",
"]@ c #717171",
"^@ c #515151",
"/@ c #3D3D3D",
"(@ c #272727",
"_@ c #323232",
":@ c #6D6C6C",
"<@ c #1C1C1C",
"[@ c #535353",
"}@ c #414141",
"|@ c #0A0A0A",
"1@ c #646363",
"2@ c #161616",
"3@ c #696969",
"4@ c #313131",
"5@ c #1F1F1F",
"6@ c #0E0E0E",
"7@ c #090909",
"8@ c #121212",
"9@ c #444444",
"0@ c #464646",
"a@ c #282828",
"b@ c #5C5C5C",
"c@ c #141414",
"d@ c #212121",
"e@ c #101010",
"f@ c #616061",
"g@ c #626262",
"h@ c #232323",
"i@ c #373737",
"j@ c #5B5959",
"k@ c #1E1E1E",
"l@ c #2C2C2C",
"m@ c #505050",
"n@ c #494646",
"o@ c #303030",
"p@ c #626162",
"q@ c #292929",
"r@ c #2A2A2A",
"s@ c #616060",
"t@ c #2D2D2D",
"u@ c #575656",
"v@ c #363636",
"w@ c #3E3E3E",
"x@ c #5F5E5E",
"y@ c #3A3A3A",
"z@ c #242424",
"A@ c #343434",
"B@ c #383838",
"C@ c #474747",
"D@ c #4F4D4D",
"E@ c #737272",
"F@ c #404040",
"G@ c #5B5A5A",
"H@ c #696869",
"I@ c #4E4E4E",
"J@ c #3D3C3C",
"K@ c #5D5B5C",
"L@ c #6A696A",
"M@ c #454545",
"N@ c #5B595A",
"O@ c #6A6969",
"P@ c #515050",
"Q@ c #595859",
"R@ c #656464",
"S@ c #413F3F",
"T@ c #747373",
"U@ c #5F5E5F",
"V@ c #605F5F",
"W@ c #595858",
"X@ c #545252",
" . + ",
" @ # $ % & * ",
" = - ; $ ",
" > , ' ) ",
" > ! ~ { ] ^ ",
" / ( _ : < . ",
" [ } | ",
" 1 2 3 4 5 6 ",
" 7 8 9 0 a b c d e f g h i j k l m n ",
" 8 o p q r s t 8 b u v w x y z A B C D E ",
" F G s H I J K L M N O P t Q R S T U V W X Y Z ` ...+. @. ",
" 7 #.$.%.L L L M N &.%.*.=.-.;.$.>.,.'.).!.~.{.].^./.(._.:. ",
" <.[.}.L L |.N 1.2.=.3.4.5.6.7.8.7.s 9.0.a.b.c.d.e.f.g.h.i.j.k. ",
" #.I L L l.m.n.I o.p.q.4.[.r.s.t.u.v.G u w.x.y.z.A.B.C.D.E.F.G. ",
" #.m.L H.1.I.I.o.*.J.K.H 5.6.7.t.L.M.N.O.u P.S P.Q.R.D.S.T.U.D.V. ",
" 8 W.L &.%.o.O *.J.3.X.[.r.Y.Z.`.G 0 +.+Q ++@+0.#+$+D.R.%+&+*+=+ ",
" c q.L I.-.=.-.;.3.-+[.;+$.>+r t u.,+b c '+9.)+!+~+R.D.R.R.D.D.{+ ",
" )+L.]+o.^+X.^+H /+P (+6._+:+t.<+8 [+}+|+++v 1+2+3+4+D.D.D.D.D.5+6+ ",
" 7+8+%.4.8+8+5.(+$.Y.s.Z.p <+u.9+0+a+b+c+d+e+f+R g+w.D.D.D.h+i+j+k+ ",
" y.>.l+H 6.m+m+n+7.Z.q `.L.o+M.p+O..+u q+7+e+r+s+t+u+5+D.D.D.D.D.D.v+w+ ",
" x+a+y+_+q r q p z+<+<.9 0 a A+B+C+c+D+1+2+!+E+F+G+H+R.D.D.D.D.D.D.I+r+ ",
" J+K+L+Z.z+<.o G F 7 M.[+ +N.c M+N+O+P+e+Q+S R+F+S+~+h+D.D.D.D.D.D.D.D.c.T+ ",
" U+i+2+t 9 a [+V+A+}+W+c X+>.Y+v )+1+Z+`+J+t+ @x..@+@@@h+@@@@D.D.D.D.D.#@P+ ",
" $@%@U+O.0 B+X+|+b+M+'+c+&@*@P+=@@+`+-@F+;@>@c.,@'@j+j+j+j+j+@@D.D.D.D.)@t+ ",
"@++@D.!@~@{@.+&@O+O+v d+7+]@@+Q+!+s+3+ @^@g+/@(@'@)@$+$+j+j+$+$+h+R.D.D.D._@:@ ",
"J+@@D.D.<@~@D+D+7+1+=@T y.r+S -@R+3+[@x.}@K+#+|@)@$+$+$+)@$+5+%@%@h+D.D.D.$@1@ ",
"3+@@D.D.D.2@G+s+3@Q+r+`+S J+F+[@S+g+}@4@5@6@)@$+)@5+5+5+5+%@7@|@+@5+@@D.D.8@E+ ",
";@@@D.D.D.D.|@~+U+9@c.g+g+0@P.U+4@a@$@~.5+$+5+5+5+%@%@7@7@|@+@I+'@'@$+D.D.'@b@ ",
"b.@@D.D.D.D.R.@@+@c@$@d@#@5@<@H+!@7@$+5+5+5+%@|@+@+@7@+@I+'@6@6@e@a.6@h+D.I+~@ ",
"-@)@D.D.D.D.h+h+@@h+j+)@j+$+$+5+$+$+$+5+5+5+7@+@I+I+I+'@~.e@8@a.a.2@2@5+D.e@E+ ",
"-@+@D.D.D.D.h+@@h+j+j+)@)@$+$+5+%@%@%@%@7@|@|@+@'@'@6@~.!@!@a.w.w.2@<@7@D.a.f@ ",
"g@!@D.D.D.D.h+j+$+$+$+$+$+5+5+%@7@7@7@|@+@I+I+'@~.~.e@!@a.c@2@i+Q.#+h@I+D.#@!+ ",
" 5@@@D.D.D.$+$+$+$+5+5+5+%@7@7@|@|@|@+@I+'@e@8@8@c@2@2@H+#+A.<@$@4+a@~.D.i@j@ ",
" P.h+D.D.D.5+%@5+%@|@|@+@|@I+I+'@6@~.~.~.e@8@8@c@H+i+Q.#+<@k@4+d@).l@a.@@m@n@ ",
" Q+I+D.D.D.$+%@7@|@|@+@I+I+'@'@~.e@e@e@e@8@c@2@H+Q.A.$@k@4+h@~+).l@o@c@6@p@ ",
" ++q@@@D.D.%@|@+@I+I++@I+6@e@!@!@8@c@c@c@w.i+A.A.$@5@#@h@~+a@r@l@u+4@a._@s@ ",
" k+5+D.D.%@I+~.6@6@6@~.8@8@a.c@w.H+i+H+Q.<@5@5@d@h@).a@l@t@o@0.,.t@6@u@ ",
" =@).D.D.)@+@8@!@!@a.c@c@w.2@H+i+#+A.<@k@4+#@h@).q@,@.@_@0.v@z.w@<@4@x@ ",
" 3+6@D.R.$+8@2@w.2@H+i+Q.#+#+A.$@5@d@h@~+).a@,@.@o@0.v@y@P.x+K+2@x@ ",
" 9@j+D.@@6@Q.A.Q.Q.A.k@5@k@4+#@h@z@(@r@l@K+_@A@v@B@U+C@S+U+w.x.D@ ",
" E@i@R.D.)@8@k@#@d@5@d@h@z@~+(@q@a@l@K+4@A@B@'.,.P.x.b./@Q.F@G@ ",
" H@4@D.D.$+c@h@r@r@q@(@(@q@,@.@K+_@A@v@y@w@P.g+^@I@B@<@J@K@ ",
" L@0.)@D.)@8@#@o@0.0._@_@_@A@v@i@y@F@M@g+m@^@M@l@k@P.N@ ",
" O@z.6@D.@@|@A.r@i@w@/@w@F@}@P.0@x+x.x.P..@4+).P@Q@ ",
" F+(@+@j+j++@c@#@t@'.}@9@0@G+'..@#@H+z@^@R@S@ ",
" R@k+0.|@5+j+)@@@R.D.D.R.R.j+c@u+F+T@ ",
" U@s@;@,.z@|@'@)@5+i+d@,.E@ ",
" E+V@W@R+W@X@ "};