]>
Commit | Line | Data |
---|---|---|
1 | #include "redis.h" | |
2 | #include "sha1.h" /* SHA1 is used for DEBUG DIGEST */ | |
3 | ||
4 | #include <arpa/inet.h> | |
5 | #include <signal.h> | |
6 | ||
7 | #ifdef HAVE_BACKTRACE | |
8 | #include <execinfo.h> | |
9 | #include <ucontext.h> | |
10 | #endif /* HAVE_BACKTRACE */ | |
11 | ||
12 | /* ================================= Debugging ============================== */ | |
13 | ||
14 | /* Compute the sha1 of string at 's' with 'len' bytes long. | |
15 | * The SHA1 is then xored againt the string pointed by digest. | |
16 | * Since xor is commutative, this operation is used in order to | |
17 | * "add" digests relative to unordered elements. | |
18 | * | |
19 | * So digest(a,b,c,d) will be the same of digest(b,a,c,d) */ | |
20 | void xorDigest(unsigned char *digest, void *ptr, size_t len) { | |
21 | SHA1_CTX ctx; | |
22 | unsigned char hash[20], *s = ptr; | |
23 | int j; | |
24 | ||
25 | SHA1Init(&ctx); | |
26 | SHA1Update(&ctx,s,len); | |
27 | SHA1Final(hash,&ctx); | |
28 | ||
29 | for (j = 0; j < 20; j++) | |
30 | digest[j] ^= hash[j]; | |
31 | } | |
32 | ||
33 | void xorObjectDigest(unsigned char *digest, robj *o) { | |
34 | o = getDecodedObject(o); | |
35 | xorDigest(digest,o->ptr,sdslen(o->ptr)); | |
36 | decrRefCount(o); | |
37 | } | |
38 | ||
39 | /* This function instead of just computing the SHA1 and xoring it | |
40 | * against diget, also perform the digest of "digest" itself and | |
41 | * replace the old value with the new one. | |
42 | * | |
43 | * So the final digest will be: | |
44 | * | |
45 | * digest = SHA1(digest xor SHA1(data)) | |
46 | * | |
47 | * This function is used every time we want to preserve the order so | |
48 | * that digest(a,b,c,d) will be different than digest(b,c,d,a) | |
49 | * | |
50 | * Also note that mixdigest("foo") followed by mixdigest("bar") | |
51 | * will lead to a different digest compared to "fo", "obar". | |
52 | */ | |
53 | void mixDigest(unsigned char *digest, void *ptr, size_t len) { | |
54 | SHA1_CTX ctx; | |
55 | char *s = ptr; | |
56 | ||
57 | xorDigest(digest,s,len); | |
58 | SHA1Init(&ctx); | |
59 | SHA1Update(&ctx,digest,20); | |
60 | SHA1Final(digest,&ctx); | |
61 | } | |
62 | ||
63 | void mixObjectDigest(unsigned char *digest, robj *o) { | |
64 | o = getDecodedObject(o); | |
65 | mixDigest(digest,o->ptr,sdslen(o->ptr)); | |
66 | decrRefCount(o); | |
67 | } | |
68 | ||
69 | /* Compute the dataset digest. Since keys, sets elements, hashes elements | |
70 | * are not ordered, we use a trick: every aggregate digest is the xor | |
71 | * of the digests of their elements. This way the order will not change | |
72 | * the result. For list instead we use a feedback entering the output digest | |
73 | * as input in order to ensure that a different ordered list will result in | |
74 | * a different digest. */ | |
75 | void computeDatasetDigest(unsigned char *final) { | |
76 | unsigned char digest[20]; | |
77 | char buf[128]; | |
78 | dictIterator *di = NULL; | |
79 | dictEntry *de; | |
80 | int j; | |
81 | uint32_t aux; | |
82 | ||
83 | memset(final,0,20); /* Start with a clean result */ | |
84 | ||
85 | for (j = 0; j < server.dbnum; j++) { | |
86 | redisDb *db = server.db+j; | |
87 | ||
88 | if (dictSize(db->dict) == 0) continue; | |
89 | di = dictGetIterator(db->dict); | |
90 | ||
91 | /* hash the DB id, so the same dataset moved in a different | |
92 | * DB will lead to a different digest */ | |
93 | aux = htonl(j); | |
94 | mixDigest(final,&aux,sizeof(aux)); | |
95 | ||
96 | /* Iterate this DB writing every entry */ | |
97 | while((de = dictNext(di)) != NULL) { | |
98 | sds key; | |
99 | robj *keyobj, *o; | |
100 | long long expiretime; | |
101 | ||
102 | memset(digest,0,20); /* This key-val digest */ | |
103 | key = dictGetKey(de); | |
104 | keyobj = createStringObject(key,sdslen(key)); | |
105 | ||
106 | mixDigest(digest,key,sdslen(key)); | |
107 | ||
108 | /* Make sure the key is loaded if VM is active */ | |
109 | o = dictGetVal(de); | |
110 | ||
111 | aux = htonl(o->type); | |
112 | mixDigest(digest,&aux,sizeof(aux)); | |
113 | expiretime = getExpire(db,keyobj); | |
114 | ||
115 | /* Save the key and associated value */ | |
116 | if (o->type == REDIS_STRING) { | |
117 | mixObjectDigest(digest,o); | |
118 | } else if (o->type == REDIS_LIST) { | |
119 | listTypeIterator *li = listTypeInitIterator(o,0,REDIS_TAIL); | |
120 | listTypeEntry entry; | |
121 | while(listTypeNext(li,&entry)) { | |
122 | robj *eleobj = listTypeGet(&entry); | |
123 | mixObjectDigest(digest,eleobj); | |
124 | decrRefCount(eleobj); | |
125 | } | |
126 | listTypeReleaseIterator(li); | |
127 | } else if (o->type == REDIS_SET) { | |
128 | setTypeIterator *si = setTypeInitIterator(o); | |
129 | robj *ele; | |
130 | while((ele = setTypeNextObject(si)) != NULL) { | |
131 | xorObjectDigest(digest,ele); | |
132 | decrRefCount(ele); | |
133 | } | |
134 | setTypeReleaseIterator(si); | |
135 | } else if (o->type == REDIS_ZSET) { | |
136 | unsigned char eledigest[20]; | |
137 | ||
138 | if (o->encoding == REDIS_ENCODING_ZIPLIST) { | |
139 | unsigned char *zl = o->ptr; | |
140 | unsigned char *eptr, *sptr; | |
141 | unsigned char *vstr; | |
142 | unsigned int vlen; | |
143 | long long vll; | |
144 | double score; | |
145 | ||
146 | eptr = ziplistIndex(zl,0); | |
147 | redisAssert(eptr != NULL); | |
148 | sptr = ziplistNext(zl,eptr); | |
149 | redisAssert(sptr != NULL); | |
150 | ||
151 | while (eptr != NULL) { | |
152 | redisAssert(ziplistGet(eptr,&vstr,&vlen,&vll)); | |
153 | score = zzlGetScore(sptr); | |
154 | ||
155 | memset(eledigest,0,20); | |
156 | if (vstr != NULL) { | |
157 | mixDigest(eledigest,vstr,vlen); | |
158 | } else { | |
159 | ll2string(buf,sizeof(buf),vll); | |
160 | mixDigest(eledigest,buf,strlen(buf)); | |
161 | } | |
162 | ||
163 | snprintf(buf,sizeof(buf),"%.17g",score); | |
164 | mixDigest(eledigest,buf,strlen(buf)); | |
165 | xorDigest(digest,eledigest,20); | |
166 | zzlNext(zl,&eptr,&sptr); | |
167 | } | |
168 | } else if (o->encoding == REDIS_ENCODING_SKIPLIST) { | |
169 | zset *zs = o->ptr; | |
170 | dictIterator *di = dictGetIterator(zs->dict); | |
171 | dictEntry *de; | |
172 | ||
173 | while((de = dictNext(di)) != NULL) { | |
174 | robj *eleobj = dictGetKey(de); | |
175 | double *score = dictGetVal(de); | |
176 | ||
177 | snprintf(buf,sizeof(buf),"%.17g",*score); | |
178 | memset(eledigest,0,20); | |
179 | mixObjectDigest(eledigest,eleobj); | |
180 | mixDigest(eledigest,buf,strlen(buf)); | |
181 | xorDigest(digest,eledigest,20); | |
182 | } | |
183 | dictReleaseIterator(di); | |
184 | } else { | |
185 | redisPanic("Unknown sorted set encoding"); | |
186 | } | |
187 | } else if (o->type == REDIS_HASH) { | |
188 | hashTypeIterator *hi; | |
189 | robj *obj; | |
190 | ||
191 | hi = hashTypeInitIterator(o); | |
192 | while (hashTypeNext(hi) != REDIS_ERR) { | |
193 | unsigned char eledigest[20]; | |
194 | ||
195 | memset(eledigest,0,20); | |
196 | obj = hashTypeCurrentObject(hi,REDIS_HASH_KEY); | |
197 | mixObjectDigest(eledigest,obj); | |
198 | decrRefCount(obj); | |
199 | obj = hashTypeCurrentObject(hi,REDIS_HASH_VALUE); | |
200 | mixObjectDigest(eledigest,obj); | |
201 | decrRefCount(obj); | |
202 | xorDigest(digest,eledigest,20); | |
203 | } | |
204 | hashTypeReleaseIterator(hi); | |
205 | } else { | |
206 | redisPanic("Unknown object type"); | |
207 | } | |
208 | /* If the key has an expire, add it to the mix */ | |
209 | if (expiretime != -1) xorDigest(digest,"!!expire!!",10); | |
210 | /* We can finally xor the key-val digest to the final digest */ | |
211 | xorDigest(final,digest,20); | |
212 | decrRefCount(keyobj); | |
213 | } | |
214 | dictReleaseIterator(di); | |
215 | } | |
216 | } | |
217 | ||
218 | void debugCommand(redisClient *c) { | |
219 | if (!strcasecmp(c->argv[1]->ptr,"segfault")) { | |
220 | *((char*)-1) = 'x'; | |
221 | } else if (!strcasecmp(c->argv[1]->ptr,"assert")) { | |
222 | if (c->argc >= 3) c->argv[2] = tryObjectEncoding(c->argv[2]); | |
223 | redisAssertWithInfo(c,c->argv[0],1 == 2); | |
224 | } else if (!strcasecmp(c->argv[1]->ptr,"reload")) { | |
225 | if (rdbSave(server.rdb_filename) != REDIS_OK) { | |
226 | addReply(c,shared.err); | |
227 | return; | |
228 | } | |
229 | emptyDb(); | |
230 | if (rdbLoad(server.rdb_filename) != REDIS_OK) { | |
231 | addReplyError(c,"Error trying to load the RDB dump"); | |
232 | return; | |
233 | } | |
234 | redisLog(REDIS_WARNING,"DB reloaded by DEBUG RELOAD"); | |
235 | addReply(c,shared.ok); | |
236 | } else if (!strcasecmp(c->argv[1]->ptr,"loadaof")) { | |
237 | emptyDb(); | |
238 | if (loadAppendOnlyFile(server.aof_filename) != REDIS_OK) { | |
239 | addReply(c,shared.err); | |
240 | return; | |
241 | } | |
242 | server.dirty = 0; /* Prevent AOF / replication */ | |
243 | redisLog(REDIS_WARNING,"Append Only File loaded by DEBUG LOADAOF"); | |
244 | addReply(c,shared.ok); | |
245 | } else if (!strcasecmp(c->argv[1]->ptr,"object") && c->argc == 3) { | |
246 | dictEntry *de; | |
247 | robj *val; | |
248 | char *strenc; | |
249 | ||
250 | if ((de = dictFind(c->db->dict,c->argv[2]->ptr)) == NULL) { | |
251 | addReply(c,shared.nokeyerr); | |
252 | return; | |
253 | } | |
254 | val = dictGetVal(de); | |
255 | strenc = strEncoding(val->encoding); | |
256 | ||
257 | addReplyStatusFormat(c, | |
258 | "Value at:%p refcount:%d " | |
259 | "encoding:%s serializedlength:%lld " | |
260 | "lru:%d lru_seconds_idle:%lu", | |
261 | (void*)val, val->refcount, | |
262 | strenc, (long long) rdbSavedObjectLen(val), | |
263 | val->lru, estimateObjectIdleTime(val)); | |
264 | } else if (!strcasecmp(c->argv[1]->ptr,"populate") && c->argc == 3) { | |
265 | long keys, j; | |
266 | robj *key, *val; | |
267 | char buf[128]; | |
268 | ||
269 | if (getLongFromObjectOrReply(c, c->argv[2], &keys, NULL) != REDIS_OK) | |
270 | return; | |
271 | for (j = 0; j < keys; j++) { | |
272 | snprintf(buf,sizeof(buf),"key:%lu",j); | |
273 | key = createStringObject(buf,strlen(buf)); | |
274 | if (lookupKeyRead(c->db,key) != NULL) { | |
275 | decrRefCount(key); | |
276 | continue; | |
277 | } | |
278 | snprintf(buf,sizeof(buf),"value:%lu",j); | |
279 | val = createStringObject(buf,strlen(buf)); | |
280 | dbAdd(c->db,key,val); | |
281 | decrRefCount(key); | |
282 | } | |
283 | addReply(c,shared.ok); | |
284 | } else if (!strcasecmp(c->argv[1]->ptr,"digest") && c->argc == 2) { | |
285 | unsigned char digest[20]; | |
286 | sds d = sdsempty(); | |
287 | int j; | |
288 | ||
289 | computeDatasetDigest(digest); | |
290 | for (j = 0; j < 20; j++) | |
291 | d = sdscatprintf(d, "%02x",digest[j]); | |
292 | addReplyStatus(c,d); | |
293 | sdsfree(d); | |
294 | } else if (!strcasecmp(c->argv[1]->ptr,"sleep") && c->argc == 3) { | |
295 | double dtime = strtod(c->argv[2]->ptr,NULL); | |
296 | long long utime = dtime*1000000; | |
297 | ||
298 | usleep(utime); | |
299 | addReply(c,shared.ok); | |
300 | } else { | |
301 | addReplyError(c, | |
302 | "Syntax error, try DEBUG [SEGFAULT|OBJECT <key>|SWAPIN <key>|SWAPOUT <key>|RELOAD]"); | |
303 | } | |
304 | } | |
305 | ||
306 | /* =========================== Crash handling ============================== */ | |
307 | ||
308 | void _redisAssert(char *estr, char *file, int line) { | |
309 | bugReportStart(); | |
310 | redisLog(REDIS_WARNING,"=== ASSERTION FAILED ==="); | |
311 | redisLog(REDIS_WARNING,"==> %s:%d '%s' is not true",file,line,estr); | |
312 | #ifdef HAVE_BACKTRACE | |
313 | server.assert_failed = estr; | |
314 | server.assert_file = file; | |
315 | server.assert_line = line; | |
316 | redisLog(REDIS_WARNING,"(forcing SIGSEGV to print the bug report.)"); | |
317 | #endif | |
318 | *((char*)-1) = 'x'; | |
319 | } | |
320 | ||
321 | void _redisAssertPrintClientInfo(redisClient *c) { | |
322 | int j; | |
323 | ||
324 | bugReportStart(); | |
325 | redisLog(REDIS_WARNING,"=== ASSERTION FAILED CLIENT CONTEXT ==="); | |
326 | redisLog(REDIS_WARNING,"client->flags = %d", c->flags); | |
327 | redisLog(REDIS_WARNING,"client->fd = %d", c->fd); | |
328 | redisLog(REDIS_WARNING,"client->argc = %d", c->argc); | |
329 | for (j=0; j < c->argc; j++) { | |
330 | char buf[128]; | |
331 | char *arg; | |
332 | ||
333 | if (c->argv[j]->type == REDIS_STRING && | |
334 | c->argv[j]->encoding == REDIS_ENCODING_RAW) | |
335 | { | |
336 | arg = (char*) c->argv[j]->ptr; | |
337 | } else { | |
338 | snprintf(buf,sizeof(buf),"Object type: %d, encoding: %d", | |
339 | c->argv[j]->type, c->argv[j]->encoding); | |
340 | arg = buf; | |
341 | } | |
342 | redisLog(REDIS_WARNING,"client->argv[%d] = \"%s\" (refcount: %d)", | |
343 | j, arg, c->argv[j]->refcount); | |
344 | } | |
345 | } | |
346 | ||
347 | void redisLogObjectDebugInfo(robj *o) { | |
348 | redisLog(REDIS_WARNING,"Object type: %d", o->type); | |
349 | redisLog(REDIS_WARNING,"Object encoding: %d", o->encoding); | |
350 | redisLog(REDIS_WARNING,"Object refcount: %d", o->refcount); | |
351 | if (o->type == REDIS_STRING && o->encoding == REDIS_ENCODING_RAW) { | |
352 | redisLog(REDIS_WARNING,"Object raw string len: %d", sdslen(o->ptr)); | |
353 | if (sdslen(o->ptr) < 4096) | |
354 | redisLog(REDIS_WARNING,"Object raw string content: \"%s\"", (char*)o->ptr); | |
355 | } else if (o->type == REDIS_LIST) { | |
356 | redisLog(REDIS_WARNING,"List length: %d", (int) listTypeLength(o)); | |
357 | } else if (o->type == REDIS_SET) { | |
358 | redisLog(REDIS_WARNING,"Set size: %d", (int) setTypeSize(o)); | |
359 | } else if (o->type == REDIS_HASH) { | |
360 | redisLog(REDIS_WARNING,"Hash size: %d", (int) hashTypeLength(o)); | |
361 | } else if (o->type == REDIS_ZSET) { | |
362 | redisLog(REDIS_WARNING,"Sorted set size: %d", (int) zsetLength(o)); | |
363 | if (o->encoding == REDIS_ENCODING_SKIPLIST) | |
364 | redisLog(REDIS_WARNING,"Skiplist level: %d", (int) ((zset*)o->ptr)->zsl->level); | |
365 | } | |
366 | } | |
367 | ||
368 | void _redisAssertPrintObject(robj *o) { | |
369 | bugReportStart(); | |
370 | redisLog(REDIS_WARNING,"=== ASSERTION FAILED OBJECT CONTEXT ==="); | |
371 | redisLogObjectDebugInfo(o); | |
372 | } | |
373 | ||
374 | void _redisAssertWithInfo(redisClient *c, robj *o, char *estr, char *file, int line) { | |
375 | if (c) _redisAssertPrintClientInfo(c); | |
376 | if (o) _redisAssertPrintObject(o); | |
377 | _redisAssert(estr,file,line); | |
378 | } | |
379 | ||
380 | void _redisPanic(char *msg, char *file, int line) { | |
381 | bugReportStart(); | |
382 | redisLog(REDIS_WARNING,"------------------------------------------------"); | |
383 | redisLog(REDIS_WARNING,"!!! Software Failure. Press left mouse button to continue"); | |
384 | redisLog(REDIS_WARNING,"Guru Meditation: %s #%s:%d",msg,file,line); | |
385 | #ifdef HAVE_BACKTRACE | |
386 | redisLog(REDIS_WARNING,"(forcing SIGSEGV in order to print the stack trace)"); | |
387 | #endif | |
388 | redisLog(REDIS_WARNING,"------------------------------------------------"); | |
389 | *((char*)-1) = 'x'; | |
390 | } | |
391 | ||
392 | void bugReportStart(void) { | |
393 | if (server.bug_report_start == 0) { | |
394 | redisLog(REDIS_WARNING, | |
395 | "\n\n=== REDIS BUG REPORT START: Cut & paste starting from here ==="); | |
396 | server.bug_report_start = 1; | |
397 | } | |
398 | } | |
399 | ||
400 | #ifdef HAVE_BACKTRACE | |
401 | static void *getMcontextEip(ucontext_t *uc) { | |
402 | #if defined(__FreeBSD__) | |
403 | return (void*) uc->uc_mcontext.mc_eip; | |
404 | #elif defined(__dietlibc__) | |
405 | return (void*) uc->uc_mcontext.eip; | |
406 | #elif defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6) | |
407 | #if __x86_64__ | |
408 | return (void*) uc->uc_mcontext->__ss.__rip; | |
409 | #elif __i386__ | |
410 | return (void*) uc->uc_mcontext->__ss.__eip; | |
411 | #else | |
412 | return (void*) uc->uc_mcontext->__ss.__srr0; | |
413 | #endif | |
414 | #elif defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6) | |
415 | #if defined(_STRUCT_X86_THREAD_STATE64) && !defined(__i386__) | |
416 | return (void*) uc->uc_mcontext->__ss.__rip; | |
417 | #else | |
418 | return (void*) uc->uc_mcontext->__ss.__eip; | |
419 | #endif | |
420 | #elif defined(__i386__) | |
421 | return (void*) uc->uc_mcontext.gregs[14]; /* Linux 32 */ | |
422 | #elif defined(__X86_64__) || defined(__x86_64__) | |
423 | return (void*) uc->uc_mcontext.gregs[16]; /* Linux 64 */ | |
424 | #elif defined(__ia64__) /* Linux IA64 */ | |
425 | return (void*) uc->uc_mcontext.sc_ip; | |
426 | #else | |
427 | return NULL; | |
428 | #endif | |
429 | } | |
430 | ||
431 | void logStackContent(void **sp) { | |
432 | int i; | |
433 | for (i = 15; i >= 0; i--) { | |
434 | if (sizeof(long) == 4) | |
435 | redisLog(REDIS_WARNING, "(%08lx) -> %08lx", sp+i, sp[i]); | |
436 | else | |
437 | redisLog(REDIS_WARNING, "(%016lx) -> %016lx", sp+i, sp[i]); | |
438 | } | |
439 | } | |
440 | ||
441 | void logRegisters(ucontext_t *uc) { | |
442 | redisLog(REDIS_WARNING, "--- REGISTERS"); | |
443 | #if defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6) | |
444 | #if defined(_STRUCT_X86_THREAD_STATE64) && !defined(__i386__) | |
445 | redisLog(REDIS_WARNING, | |
446 | "\n" | |
447 | "RAX:%016lx RBX:%016lx\nRCX:%016lx RDX:%016lx\n" | |
448 | "RDI:%016lx RSI:%016lx\nRBP:%016lx RSP:%016lx\n" | |
449 | "R8 :%016lx R9 :%016lx\nR10:%016lx R11:%016lx\n" | |
450 | "R12:%016lx R13:%016lx\nR14:%016lx R15:%016lx\n" | |
451 | "RIP:%016lx EFL:%016lx\nCS :%016lx FS:%016lx GS:%016lx", | |
452 | uc->uc_mcontext->__ss.__rax, | |
453 | uc->uc_mcontext->__ss.__rbx, | |
454 | uc->uc_mcontext->__ss.__rcx, | |
455 | uc->uc_mcontext->__ss.__rdx, | |
456 | uc->uc_mcontext->__ss.__rdi, | |
457 | uc->uc_mcontext->__ss.__rsi, | |
458 | uc->uc_mcontext->__ss.__rbp, | |
459 | uc->uc_mcontext->__ss.__rsp, | |
460 | uc->uc_mcontext->__ss.__r8, | |
461 | uc->uc_mcontext->__ss.__r9, | |
462 | uc->uc_mcontext->__ss.__r10, | |
463 | uc->uc_mcontext->__ss.__r11, | |
464 | uc->uc_mcontext->__ss.__r12, | |
465 | uc->uc_mcontext->__ss.__r13, | |
466 | uc->uc_mcontext->__ss.__r14, | |
467 | uc->uc_mcontext->__ss.__r15, | |
468 | uc->uc_mcontext->__ss.__rip, | |
469 | uc->uc_mcontext->__ss.__rflags, | |
470 | uc->uc_mcontext->__ss.__cs, | |
471 | uc->uc_mcontext->__ss.__fs, | |
472 | uc->uc_mcontext->__ss.__gs | |
473 | ); | |
474 | logStackContent((void**)uc->uc_mcontext->__ss.__rsp); | |
475 | #else | |
476 | redisLog(REDIS_WARNING, | |
477 | "\n" | |
478 | "EAX:%08lx EBX:%08lx ECX:%08lx EDX:%08lx\n" | |
479 | "EDI:%08lx ESI:%08lx EBP:%08lx ESP:%08lx\n" | |
480 | "SS:%08lx EFL:%08lx EIP:%08lx CS :%08lx\n" | |
481 | "DS:%08lx ES:%08lx FS :%08lx GS :%08lx", | |
482 | uc->uc_mcontext->__ss.__eax, | |
483 | uc->uc_mcontext->__ss.__ebx, | |
484 | uc->uc_mcontext->__ss.__ecx, | |
485 | uc->uc_mcontext->__ss.__edx, | |
486 | uc->uc_mcontext->__ss.__edi, | |
487 | uc->uc_mcontext->__ss.__esi, | |
488 | uc->uc_mcontext->__ss.__ebp, | |
489 | uc->uc_mcontext->__ss.__esp, | |
490 | uc->uc_mcontext->__ss.__ss, | |
491 | uc->uc_mcontext->__ss.__eflags, | |
492 | uc->uc_mcontext->__ss.__eip, | |
493 | uc->uc_mcontext->__ss.__cs, | |
494 | uc->uc_mcontext->__ss.__ds, | |
495 | uc->uc_mcontext->__ss.__es, | |
496 | uc->uc_mcontext->__ss.__fs, | |
497 | uc->uc_mcontext->__ss.__gs | |
498 | ); | |
499 | logStackContent((void**)uc->uc_mcontext->__ss.__esp); | |
500 | #endif | |
501 | #elif defined(__i386__) | |
502 | redisLog(REDIS_WARNING, | |
503 | "\n" | |
504 | "EAX:%08lx EBX:%08lx ECX:%08lx EDX:%08lx\n" | |
505 | "EDI:%08lx ESI:%08lx EBP:%08lx ESP:%08lx\n" | |
506 | "SS :%08lx EFL:%08lx EIP:%08lx CS:%08lx\n" | |
507 | "DS :%08lx ES :%08lx FS :%08lx GS:%08lx", | |
508 | uc->uc_mcontext.gregs[11], | |
509 | uc->uc_mcontext.gregs[8], | |
510 | uc->uc_mcontext.gregs[10], | |
511 | uc->uc_mcontext.gregs[9], | |
512 | uc->uc_mcontext.gregs[4], | |
513 | uc->uc_mcontext.gregs[5], | |
514 | uc->uc_mcontext.gregs[6], | |
515 | uc->uc_mcontext.gregs[7], | |
516 | uc->uc_mcontext.gregs[18], | |
517 | uc->uc_mcontext.gregs[17], | |
518 | uc->uc_mcontext.gregs[14], | |
519 | uc->uc_mcontext.gregs[15], | |
520 | uc->uc_mcontext.gregs[3], | |
521 | uc->uc_mcontext.gregs[2], | |
522 | uc->uc_mcontext.gregs[1], | |
523 | uc->uc_mcontext.gregs[0] | |
524 | ); | |
525 | logStackContent((void**)uc->uc_mcontext.gregs[7]); | |
526 | #elif defined(__X86_64__) || defined(__x86_64__) | |
527 | redisLog(REDIS_WARNING, | |
528 | "\n" | |
529 | "RAX:%016lx RBX:%016lx\nRCX:%016lx RDX:%016lx\n" | |
530 | "RDI:%016lx RSI:%016lx\nRBP:%016lx RSP:%016lx\n" | |
531 | "R8 :%016lx R9 :%016lx\nR10:%016lx R11:%016lx\n" | |
532 | "R12:%016lx R13:%016lx\nR14:%016lx R15:%016lx\n" | |
533 | "RIP:%016lx EFL:%016lx\nCSGSFS:%016lx", | |
534 | uc->uc_mcontext.gregs[13], | |
535 | uc->uc_mcontext.gregs[11], | |
536 | uc->uc_mcontext.gregs[14], | |
537 | uc->uc_mcontext.gregs[12], | |
538 | uc->uc_mcontext.gregs[8], | |
539 | uc->uc_mcontext.gregs[9], | |
540 | uc->uc_mcontext.gregs[10], | |
541 | uc->uc_mcontext.gregs[15], | |
542 | uc->uc_mcontext.gregs[0], | |
543 | uc->uc_mcontext.gregs[1], | |
544 | uc->uc_mcontext.gregs[2], | |
545 | uc->uc_mcontext.gregs[3], | |
546 | uc->uc_mcontext.gregs[4], | |
547 | uc->uc_mcontext.gregs[5], | |
548 | uc->uc_mcontext.gregs[6], | |
549 | uc->uc_mcontext.gregs[7], | |
550 | uc->uc_mcontext.gregs[16], | |
551 | uc->uc_mcontext.gregs[17], | |
552 | uc->uc_mcontext.gregs[18] | |
553 | ); | |
554 | logStackContent((void**)uc->uc_mcontext.gregs[15]); | |
555 | #else | |
556 | redisLog(REDIS_WARNING, | |
557 | " Dumping of registers not supported for this OS/arch"); | |
558 | #endif | |
559 | } | |
560 | ||
561 | /* Logs the stack trace using the backtrace() call. */ | |
562 | sds getStackTrace(ucontext_t *uc) { | |
563 | void *trace[100]; | |
564 | int i, trace_size = 0; | |
565 | char **messages = NULL; | |
566 | sds st = sdsempty(); | |
567 | ||
568 | /* Generate the stack trace */ | |
569 | trace_size = backtrace(trace, 100); | |
570 | ||
571 | /* overwrite sigaction with caller's address */ | |
572 | if (getMcontextEip(uc) != NULL) { | |
573 | trace[1] = getMcontextEip(uc); | |
574 | } | |
575 | messages = backtrace_symbols(trace, trace_size); | |
576 | for (i=1; i<trace_size; ++i) { | |
577 | st = sdscat(st,messages[i]); | |
578 | st = sdscatlen(st,"\n",1); | |
579 | } | |
580 | zlibc_free(messages); | |
581 | return st; | |
582 | } | |
583 | ||
584 | /* Log information about the "current" client, that is, the client that is | |
585 | * currently being served by Redis. May be NULL if Redis is not serving a | |
586 | * client right now. */ | |
587 | void logCurrentClient(void) { | |
588 | if (server.current_client == NULL) return; | |
589 | ||
590 | redisClient *cc = server.current_client; | |
591 | sds client; | |
592 | int j; | |
593 | ||
594 | redisLog(REDIS_WARNING, "--- CURRENT CLIENT INFO"); | |
595 | client = getClientInfoString(cc); | |
596 | redisLog(REDIS_WARNING,"client: %s", client); | |
597 | sdsfree(client); | |
598 | for (j = 0; j < cc->argc; j++) { | |
599 | robj *decoded; | |
600 | ||
601 | decoded = getDecodedObject(cc->argv[j]); | |
602 | redisLog(REDIS_WARNING,"argv[%d]: '%s'", j, (char*)decoded->ptr); | |
603 | decrRefCount(decoded); | |
604 | } | |
605 | /* Check if the first argument, usually a key, is found inside the | |
606 | * selected DB, and if so print info about the associated object. */ | |
607 | if (cc->argc >= 1) { | |
608 | robj *val, *key; | |
609 | dictEntry *de; | |
610 | ||
611 | key = getDecodedObject(cc->argv[1]); | |
612 | de = dictFind(cc->db->dict, key->ptr); | |
613 | if (de) { | |
614 | val = dictGetVal(de); | |
615 | redisLog(REDIS_WARNING,"key '%s' found in DB containing the following object:", key->ptr); | |
616 | redisLogObjectDebugInfo(val); | |
617 | } | |
618 | decrRefCount(key); | |
619 | } | |
620 | } | |
621 | ||
622 | void sigsegvHandler(int sig, siginfo_t *info, void *secret) { | |
623 | ucontext_t *uc = (ucontext_t*) secret; | |
624 | sds infostring, clients, st; | |
625 | struct sigaction act; | |
626 | REDIS_NOTUSED(info); | |
627 | ||
628 | bugReportStart(); | |
629 | redisLog(REDIS_WARNING, | |
630 | " Redis %s crashed by signal: %d", REDIS_VERSION, sig); | |
631 | redisLog(REDIS_WARNING, | |
632 | " Failed assertion: %s (%s:%d)", server.assert_failed, | |
633 | server.assert_file, server.assert_line); | |
634 | ||
635 | /* Log the stack trace */ | |
636 | st = getStackTrace(uc); | |
637 | redisLog(REDIS_WARNING, "--- STACK TRACE\n%s", st); | |
638 | sdsfree(st); | |
639 | ||
640 | /* Log INFO and CLIENT LIST */ | |
641 | redisLog(REDIS_WARNING, "--- INFO OUTPUT"); | |
642 | infostring = genRedisInfoString("all"); | |
643 | infostring = sdscatprintf(infostring, "hash_init_value: %u\n", | |
644 | dictGetHashFunctionSeed()); | |
645 | redisLogRaw(REDIS_WARNING, infostring); | |
646 | redisLog(REDIS_WARNING, "--- CLIENT LIST OUTPUT"); | |
647 | clients = getAllClientsInfoString(); | |
648 | redisLogRaw(REDIS_WARNING, clients); | |
649 | sdsfree(infostring); | |
650 | sdsfree(clients); | |
651 | ||
652 | /* Log the current client */ | |
653 | logCurrentClient(); | |
654 | ||
655 | /* Log dump of processor registers */ | |
656 | logRegisters(uc); | |
657 | ||
658 | redisLog(REDIS_WARNING, | |
659 | "\n=== REDIS BUG REPORT END. Make sure to include from START to END. ===\n\n" | |
660 | " Please report the crash opening an issue on github:\n\n" | |
661 | " http://github.com/antirez/redis/issues\n\n" | |
662 | " Suspect RAM error? Use redis-server --test-memory to veryfy it.\n\n" | |
663 | ); | |
664 | /* free(messages); Don't call free() with possibly corrupted memory. */ | |
665 | if (server.daemonize) unlink(server.pidfile); | |
666 | ||
667 | /* Make sure we exit with the right signal at the end. So for instance | |
668 | * the core will be dumped if enabled. */ | |
669 | sigemptyset (&act.sa_mask); | |
670 | act.sa_flags = SA_NODEFER | SA_ONSTACK | SA_RESETHAND; | |
671 | act.sa_handler = SIG_DFL; | |
672 | sigaction (sig, &act, NULL); | |
673 | kill(getpid(),sig); | |
674 | } | |
675 | #endif /* HAVE_BACKTRACE */ | |
676 | ||
677 | /* =========================== Software Watchdog ============================ */ | |
678 | #include <sys/time.h> | |
679 | ||
680 | void watchdogSignalHandler(int sig, siginfo_t *info, void *secret) { | |
681 | ucontext_t *uc = (ucontext_t*) secret; | |
682 | REDIS_NOTUSED(info); | |
683 | REDIS_NOTUSED(sig); | |
684 | sds st, log; | |
685 | ||
686 | log = sdsnew("\n--- WATCHDOG TIMER EXPIRED ---\n"); | |
687 | #ifdef HAVE_BACKTRACE | |
688 | st = getStackTrace(uc); | |
689 | #else | |
690 | st = sdsnew("Sorry: no support for backtrace().\n"); | |
691 | #endif | |
692 | log = sdscatsds(log,st); | |
693 | log = sdscat(log,"------\n"); | |
694 | redisLogFromHandler(REDIS_WARNING,log); | |
695 | sdsfree(st); | |
696 | sdsfree(log); | |
697 | } | |
698 | ||
699 | /* Schedule a SIGALRM delivery after the specified period in milliseconds. | |
700 | * If a timer is already scheduled, this function will re-schedule it to the | |
701 | * specified time. If period is 0 the current timer is disabled. */ | |
702 | void watchdogScheduleSignal(int period) { | |
703 | struct itimerval it; | |
704 | ||
705 | /* Will stop the timer if period is 0. */ | |
706 | it.it_value.tv_sec = period/1000; | |
707 | it.it_value.tv_usec = (period%1000)*1000; | |
708 | /* Don't automatically restart. */ | |
709 | it.it_interval.tv_sec = 0; | |
710 | it.it_interval.tv_usec = 0; | |
711 | setitimer(ITIMER_REAL, &it, NULL); | |
712 | } | |
713 | ||
714 | /* Enable the software watchdong with the specified period in milliseconds. */ | |
715 | void enableWatchdog(int period) { | |
716 | if (server.watchdog_period == 0) { | |
717 | struct sigaction act; | |
718 | ||
719 | /* Watchdog was actually disabled, so we have to setup the signal | |
720 | * handler. */ | |
721 | sigemptyset(&act.sa_mask); | |
722 | act.sa_flags = SA_NODEFER | SA_ONSTACK | SA_SIGINFO; | |
723 | act.sa_sigaction = watchdogSignalHandler; | |
724 | sigaction(SIGALRM, &act, NULL); | |
725 | } | |
726 | if (period < 200) period = 200; /* We don't accept periods < 200 ms. */ | |
727 | watchdogScheduleSignal(period); /* Adjust the current timer. */ | |
728 | server.watchdog_period = period; | |
729 | } | |
730 | ||
731 | /* Disable the software watchdog. */ | |
732 | void disableWatchdog(void) { | |
733 | struct sigaction act; | |
734 | if (server.watchdog_period == 0) return; /* Already disabled. */ | |
735 | watchdogScheduleSignal(0); /* Stop the current timer. */ | |
736 | ||
737 | /* Set the signal handler to SIG_IGN, this will also remove pending | |
738 | * signals from the queue. */ | |
739 | sigemptyset(&act.sa_mask); | |
740 | act.sa_flags = 0; | |
741 | act.sa_handler = SIG_IGN; | |
742 | sigaction(SIGALRM, &act, NULL); | |
743 | server.watchdog_period = 0; | |
744 | } |