]> git.saurik.com Git - apple/security.git/blob - libsecurity_utilities/lib/debugging.cpp
Security-55179.1.tar.gz
[apple/security.git] / libsecurity_utilities / lib / debugging.cpp
1 /*
2 * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24
25 //
26 // debugging - non-trivial debugging support
27 //
28 #include <security_utilities/debugsupport.h>
29 #include <security_utilities/globalizer.h>
30 #include <cstdarg>
31 #include <ctype.h>
32
33 #define SYSLOG_NAMES // compile syslog name tables
34 #include <syslog.h>
35
36 #include <cxxabi.h> // for name demangling
37 #include <mach-o/dyld.h> // for _NSGetExecutablePath
38
39 // enable kernel tracing
40 #define ENABLE_SECTRACE 1
41
42
43 namespace Security {
44 namespace Debug {
45
46
47 //
48 // Dump facility
49 //
50 bool dumping(const char *scope)
51 {
52 #if defined(NDEBUG_STUBS)
53 return false;
54 #else
55 return Target::get().dump(scope);
56 #endif
57 }
58
59 void dump(const char *format, ...)
60 {
61 #if !defined(NDEBUG_CODE)
62 va_list args;
63 va_start(args, format);
64 Target::get().dump(format, args);
65 va_end(args);
66 #endif
67 }
68
69 void dumpData(const void *ptr, size_t size)
70 {
71 #if !defined(NDEBUG_CODE)
72 const char *addr = reinterpret_cast<const char *>(ptr);
73 const char *end = addr + size;
74 bool isText = true;
75 for (const char *p = addr; p < end; p++)
76 if (!isprint(*p)) { isText = false; break; }
77
78 if (isText) {
79 dump("\"");
80 for (const char *p = addr; p < end; p++)
81 dump("%c", *p);
82 dump("\"");
83 } else {
84 dump("0x");
85 for (const char *p = addr; p < end; p++)
86 dump("%2.2x", static_cast<unsigned char>(*p));
87 }
88 #endif //NDEBUG_STUBS
89 }
90
91 void dumpData(const char *title, const void *ptr, size_t size)
92 {
93 #if !defined(NDEBUG_CODE)
94 dump("%s: ", title);
95 dumpData(ptr, size);
96 dump("\n");
97 #endif //NDEBUG_STUBS
98 }
99
100
101 //
102 // Turn a C++ typeid into a nice type name.
103 // This uses the C++ ABI where available.
104 // We're stripping out a few C++ prefixes; they're pretty redundant (and obvious).
105 //
106 string makeTypeName(const type_info &type)
107 {
108 int status;
109 char *cname = abi::__cxa_demangle(type.name(), NULL, NULL, &status);
110 string name = !strncmp(cname, "Security::", 10) ? (cname + 10) :
111 !strncmp(cname, "std::", 5) ? (cname + 5) :
112 cname;
113 ::free(cname); // yes, really (ABI rules)
114 return name;
115 }
116
117
118 //
119 // Target initialization.
120 // This where we should do all "first time" initializations.
121 //
122 #if !defined(NDEBUG_CODE)
123
124 char Target::progName[maxProgNameLength + 1];
125 unsigned int Target::PerThread::lastUsed;
126
127 Target::Target()
128 : showScope(false), showThread(false), showProc(false), showDate(false),
129 sink(NULL)
130 {
131 // put into singleton slot if first
132 if (singleton == NULL)
133 singleton = this;
134
135 // insert terminate handler
136 if (!previousTerminator) // first time we do this
137 previousTerminator = set_terminate(terminator);
138
139 // get program name
140 char execPath[PATH_MAX];
141 uint32_t length = sizeof(execPath);
142 if (_NSGetExecutablePath(execPath, &length)) {
143 strcpy(progName, "unknown");
144 } else {
145 const char *p = strrchr(execPath, '/');
146 if (p)
147 p++;
148 else
149 p = execPath;
150 unsigned plen = strlen(p);
151 if (plen > maxProgNameLength) // too long
152 p += plen - maxProgNameLength; // take rear
153 strcpy(progName, p);
154 }
155 }
156
157 Target::~Target()
158 {
159 }
160
161
162 static void addScope(char *&bufp, const char *scope)
163 {
164 if (const char *sep = strchr(scope, ',')) {
165 bufp += sprintf(bufp, "%-*s", Name::maxLength, (const char *)Name(scope, sep));
166 } else { // single scope
167 bufp += sprintf(bufp, "%-*s", Name::maxLength, scope);
168 }
169 }
170
171
172 //
173 // The core logging function of a Target
174 //
175 void Target::message(const char *scope, const char *format, va_list args)
176 {
177 if (logSelector(scope)) {
178 // note: messageConstructionSize is big enough for all prefixes constructed
179 char buffer[messageConstructionSize]; // building the message here
180 char *bufp = buffer;
181
182 // date option
183 if (showDate && sink->needsDate) {
184 time_t now = time(NULL);
185 char *date = ctime(&now);
186 date[19] = '\0';
187 bufp += sprintf(bufp, "%s ", date + 4); // Nov 24 18:22:48
188 }
189
190 // leading scope
191 if (showScope && scope)
192 addScope(bufp, scope);
193
194 if (showProc || showThread) {
195 char sub[maxProgNameLength + 20];
196 unsigned plen = 0;
197 if (showProc && showThread)
198 plen = sprintf(sub, "%s[%d]", progName, getpid());
199 else if (showProc)
200 plen = sprintf(sub, "%s", progName);
201 else
202 plen = sprintf(sub, "[%d]", getpid());
203 unsigned int id = perThread().id;
204 if (id > 1)
205 plen += sprintf(sub + plen, ":%d", id);
206 if (plen <= procLength)
207 bufp += sprintf(bufp, "%-*s ", int(procLength), sub);
208 else
209 bufp += sprintf(bufp, "%s ", sub + plen - procLength);
210 }
211
212 // scope after proc/thread/pid
213 if (showScopeRight && scope)
214 addScope(bufp, scope);
215
216 // now stuff the message body in, slightly roasted
217 size_t left = buffer + sizeof(buffer) - bufp - 1; // reserve one
218 size_t written = vsnprintf(bufp, left, format, args);
219 for (char *p = bufp; *p; p++)
220 if (!isprint(*p))
221 *p = '?';
222 if (written >= left) { // snprintf overflowed
223 bufp += left;
224 strcpy(bufp - 3, "...");
225 } else
226 bufp += written;
227
228 // now append a newline and a null
229 bufp[0] = '\n';
230 bufp[1] = '\0';
231
232 // submit to sink (do not count newline and null in count)
233 sink->put(buffer, bufp - buffer);
234 }
235 }
236
237 bool Target::debugging(const char *scope)
238 {
239 return logSelector(scope);
240 }
241
242
243 //
244 // The core debug-dump function of a target
245 //
246 void Target::dump(const char *format, va_list args)
247 {
248 char buffer[messageConstructionSize]; // building the message here
249 vsnprintf(buffer, sizeof(buffer), format, args);
250 for (char *p = buffer; *p; p++)
251 if (!isprint(*p) && !isspace(*p) || *p == '\r')
252 *p = '?';
253 sink->dump(buffer);
254 }
255
256 bool Target::dump(const char *scope)
257 {
258 return dumpSelector(scope);
259 }
260
261
262 //
263 // Selector objects.
264 //
265 Target::Selector::Selector() : useSet(false), negate(false)
266 { }
267
268 void Target::Selector::operator = (const char *scope)
269 {
270 if (scope) {
271 // initial values
272 if (!strcmp(scope, "all")) {
273 useSet = false;
274 negate = true;
275 } else if (!strcmp(scope, "none")) {
276 useSet = negate = false;
277 } else {
278 useSet = true;
279 enableSet.erase(enableSet.begin(), enableSet.end());
280 if (scope[0] == '-') {
281 negate = true;
282 scope++;
283 } else
284 negate = false;
285 while (const char *sep = strchr(scope, ',')) {
286 enableSet.insert(Name(scope, sep));
287 scope = sep + 1;
288 }
289 enableSet.insert(scope);
290 }
291 } else {
292 useSet = negate = false;
293 }
294 }
295
296 bool Target::Selector::operator () (const char *scope) const
297 {
298 // a scope of NULL is a special override; it always qualifies
299 if (scope == NULL)
300 return true;
301
302 if (useSet) {
303 while (const char *sep = strchr(scope, ',')) {
304 if (enableSet.find(Name(scope, sep)) != enableSet.end())
305 return !negate;
306 scope = sep + 1;
307 }
308 return (enableSet.find(scope) != enableSet.end()) != negate;
309 } else {
310 return negate;
311 }
312 }
313
314
315 //
316 // Establish Target state from the environment
317 //
318 void Target::setFromEnvironment()
319 {
320 // set scopes
321 logSelector = getenv("DEBUGSCOPE");
322 dumpSelector = getenv("DEBUGDUMP");
323
324 //
325 // Set and configure destination. Currently available:
326 // /some/where -> that file
327 // LOG_SOMETHING -> syslog facility
328 // >&number -> that (already) open (for write or append) file descriptor
329 // anything else -> try as a filename sight unseen [may change]
330 // DEBUGDEST not set -> stderr
331 // anything in error -> stderr (with an error message on it)
332 //
333 if (const char *dest = getenv("DEBUGDEST")) {
334 if (dest[0] == '/') { // full pathname, write to file
335 to(dest);
336 } else if (!strncmp(dest, "LOG_", 4)) { // syslog
337 int facility = LOG_DAEMON;
338 for (CODE *cp = facilitynames; cp->c_name; cp++)
339 if (!strcmp(dest, cp->c_name))
340 facility = cp->c_val;
341 to(facility | LOG_DEBUG);
342 } else if (!strncmp(dest, ">&", 2)) { // to file descriptor
343 int fd = atoi(dest+2);
344 if (FILE *f = fdopen(fd, "a")) {
345 to(f);
346 } else {
347 to(stderr);
348 secdebug("", "cannot log to fd[%d]: %s", fd, strerror(errno));
349 }
350 } else { // if everything else fails, write a file
351 to(dest);
352 }
353 } else { // default destination is stderr
354 to(stderr);
355 }
356 configure();
357 }
358
359
360 void Target::configure()
361 {
362 configure(getenv("DEBUGOPTIONS"));
363 }
364
365 void Target::configure(const char *config)
366 {
367 // configure global options
368 showScopeRight = config && strstr(config, "rscope");
369 showScope = !showScopeRight && config && strstr(config, "scope");
370 showThread = config && (strstr(config, "thread") || strstr(config, "pid")); // (legacy)
371 showProc = config && strstr(config, "proc");
372 showDate = config && strstr(config, "date");
373
374 // configure sink
375 if (sink)
376 sink->configure(config);
377 }
378
379
380 //
381 // Explicit destination assignments
382 //
383 void Target::to(Sink *s)
384 {
385 delete sink;
386 sink = s;
387 }
388
389 void Target::to(FILE *file)
390 {
391 to(new FileSink(file));
392 }
393
394 void Target::to(const char *filename)
395 {
396 if (FILE *f = fopen(filename, "a")) {
397 to(new FileSink(f));
398 } else {
399 to(stderr);
400 secdebug("", "cannot debug to \"%s\": %s", filename, strerror(errno));
401 }
402 }
403
404 void Target::to(int syslogPriority)
405 {
406 to(new SyslogSink(syslogPriority));
407 }
408
409
410 //
411 // Making and retrieving the default singleton
412 //
413 Target *Target::singleton;
414
415 Target &Target::get()
416 {
417 if (singleton == NULL) {
418 Target *t = new Target;
419 t->setFromEnvironment();
420 }
421 return *singleton;
422 }
423
424
425 //
426 // Standard sink implementations
427 //
428 Target::Sink::~Sink()
429 { }
430
431 void Target::Sink::dump(const char *)
432 { }
433
434 void Target::Sink::configure(const char *)
435 { }
436
437
438 //
439 // The terminate handler installed when a Target is created
440 //
441 terminate_handler Target::previousTerminator;
442
443 void Target::terminator()
444 {
445 secdebug("exception", "uncaught exception terminates program");
446 previousTerminator();
447 secdebug("exception", "prior termination handler failed to abort; forcing abort");
448 abort();
449 }
450
451
452 //
453 // File sinks (write to file via stdio)
454 //
455 void FileSink::put(const char *inbuf, unsigned int length)
456 {
457 fwrite(inbuf, 1, length + 1, file); // do pick up the trailing newline
458 }
459
460 void FileSink::dump(const char *text)
461 {
462 fputs(text, file);
463 }
464
465 void FileSink::configure(const char *options)
466 {
467 if (options == NULL || !strstr(options, "noflush")) {
468 // we mean "if the file isn't unbuffered", but what's the portable way to say that?
469 if (file != stderr)
470 setlinebuf(file);
471 }
472 }
473
474
475 //
476 // Syslog sinks (write to syslog)
477 //
478 void SyslogSink::put(const char *buffer, unsigned int length)
479 {
480 syslog(priority, "%1.*s", length, buffer); // don't pick up trailing newline
481 }
482
483 void SyslogSink::dump(const char *text)
484 {
485 // add to dump buffer
486 snprintf(dumpPtr, dumpBuffer + dumpBufferSize - dumpPtr, "%s", text);
487
488 // take off full lines and submit
489 char *p = dumpBase;
490 while (char *q = strchr(p, '\n')) {
491 *q++ = '\0'; // terminate/break
492 syslog(priority, " @@ %s", p);
493 p = q;
494 }
495
496 if (*p) { // left-over unterminated line segment in buffer
497 dumpPtr = p + strlen(p);
498 if ((dumpBase = p) > dumpBuffer + dumpBufferSize / 2) {
499 // shift buffer down to make room
500 memmove(dumpBuffer, dumpBase, dumpPtr - dumpBase);
501 dumpPtr -= (dumpBase - dumpBuffer);
502 dumpBase = dumpBuffer;
503 }
504 } else { // buffer is empty; reset to start
505 dumpBase = dumpPtr = dumpBuffer;
506 }
507 }
508
509 void SyslogSink::configure(const char *options)
510 {
511 }
512
513 #endif //NDEBUG_CODE
514
515
516 } // end namespace Debug
517 } // end namespace Security