xref: /xnu-11215/libkern/os/log_encode.c (revision 8d741a5d)
1 /*
2  * Copyright (c) 2015-2020 Apple 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 #include <stdbool.h>
25 #include <firehose/tracepoint_private.h>
26 #include <kern/assert.h>
27 #include <kern/counter.h>
28 #include <kern/locks.h>
29 #include <pexpert/pexpert.h>
30 #include <sys/param.h>
31 
32 #if __has_feature(ptrauth_calls)
33 #include <mach/vm_param.h>
34 #include <ptrauth.h>
35 #endif /* __has_feature(ptrauth_calls) */
36 
37 #include "log_encode.h"
38 #include "log_internal.h"
39 #include "log_mem.h"
40 
41 #define LOG_FMT_MAX_PRECISION (1024)
42 #define log_context_cursor(ctx) &(ctx)->ctx_hdr->hdr_data[(ctx)->ctx_content_off]
43 #define TRACEPOINT_BUF_MAX_SIZE (64)
44 
45 typedef struct {
46 	uint8_t *tp_buf;
47 	size_t  tp_size;
48 } tracepoint_buf_t;
49 
50 SCALABLE_COUNTER_DEFINE(oslog_p_fmt_invalid_msgcount);
51 SCALABLE_COUNTER_DEFINE(oslog_p_fmt_max_args_msgcount);
52 SCALABLE_COUNTER_DEFINE(oslog_p_truncated_msgcount);
53 
54 extern boolean_t doprnt_hide_pointers;
55 
56 static bool
is_digit(char ch)57 is_digit(char ch)
58 {
59 	return (ch >= '0') && (ch <= '9');
60 }
61 
62 static bool
is_kernel_pointer(void * arg,size_t arg_len)63 is_kernel_pointer(void *arg, size_t arg_len)
64 {
65 	if (arg_len < sizeof(void *)) {
66 		return false;
67 	}
68 
69 	unsigned long long value = 0;
70 	assert(arg_len <= sizeof(value));
71 	(void) memcpy(&value, arg, arg_len);
72 
73 	return VM_KERNEL_ADDRESS(value);
74 }
75 
76 static void
log_context_cursor_advance(os_log_context_t ctx,size_t amount)77 log_context_cursor_advance(os_log_context_t ctx, size_t amount)
78 {
79 	ctx->ctx_content_off += amount;
80 	assert(log_context_cursor(ctx) <= (ctx->ctx_buffer + ctx->ctx_buffer_sz));
81 }
82 
83 static bool
log_fits(os_log_context_t ctx,size_t data_size)84 log_fits(os_log_context_t ctx, size_t data_size)
85 {
86 	return (ctx->ctx_content_off + data_size) <= ctx->ctx_content_sz;
87 }
88 
89 static bool
log_fits_cmd(os_log_context_t ctx,size_t data_size)90 log_fits_cmd(os_log_context_t ctx, size_t data_size)
91 {
92 	return log_fits(ctx, sizeof(*ctx->ctx_hdr) + data_size);
93 }
94 
95 static void
log_range_update(os_log_fmt_range_t range,uint16_t offset,uint16_t length)96 log_range_update(os_log_fmt_range_t range, uint16_t offset, uint16_t length)
97 {
98 	range->offset = offset;
99 	/*
100 	 * Truncated flag may have already been set earlier, hence do not
101 	 * overwrite it blindly.
102 	 */
103 	if (length < range->length) {
104 		range->truncated = true;
105 	}
106 	range->length = length;
107 }
108 
109 /*
110  * Stores a command in the main section. The value itself is wrapped in
111  * the os_log_fmt_cmd_t struct.
112  */
113 static void
log_add_cmd(os_log_context_t ctx,os_log_fmt_cmd_type_t type,uint8_t flags,void * arg,size_t arg_size)114 log_add_cmd(os_log_context_t ctx, os_log_fmt_cmd_type_t type, uint8_t flags,
115     void *arg, size_t arg_size)
116 {
117 	os_log_fmt_cmd_t cmd;
118 	const size_t cmd_sz = sizeof(*cmd) + arg_size;
119 
120 	assert(log_fits_cmd(ctx, cmd_sz));
121 	assert(arg_size <= UINT8_MAX);
122 
123 	cmd = (os_log_fmt_cmd_t)log_context_cursor(ctx);
124 	cmd->cmd_type = type;
125 	cmd->cmd_flags = flags;
126 	cmd->cmd_size = (uint8_t)arg_size;
127 	(void) memcpy(cmd->cmd_data, arg, cmd->cmd_size);
128 
129 	assert(cmd_sz == sizeof(*cmd) + cmd->cmd_size);
130 	log_context_cursor_advance(ctx, cmd_sz);
131 }
132 
133 /*
134  * Collect details about argument which needs to be stored in the pubdata
135  * section.
136  */
137 static void
log_collect_public_range_data(os_log_context_t ctx,os_log_fmt_range_t range,void * arg)138 log_collect_public_range_data(os_log_context_t ctx, os_log_fmt_range_t range, void *arg)
139 {
140 	ctx->ctx_pubdata[ctx->ctx_pubdata_cnt++] = (char *)arg;
141 	ctx->ctx_pubdata_sz += range->length;
142 }
143 
144 static void
log_add_range_data(os_log_context_t ctx,os_log_fmt_range_t range,void * arg)145 log_add_range_data(os_log_context_t ctx, os_log_fmt_range_t range, void *arg)
146 {
147 	assert(log_fits(ctx, range->length));
148 	(void) memcpy(log_context_cursor(ctx), arg, range->length);
149 	log_context_cursor_advance(ctx, range->length);
150 }
151 
152 static struct os_log_fmt_range_s
log_create_range(os_log_context_t ctx,size_t arg_len)153 log_create_range(os_log_context_t ctx, size_t arg_len)
154 {
155 	const size_t final_arg_len = MIN(arg_len, UINT16_MAX);
156 
157 	return (struct os_log_fmt_range_s) {
158 		       .offset = ctx->ctx_pubdata_sz,
159 		       .length = (uint16_t)final_arg_len,
160 		       .truncated = (final_arg_len < arg_len)
161 	};
162 }
163 
164 static int
log_add_range_arg(os_log_context_t ctx,os_log_fmt_cmd_type_t type,os_log_fmt_cmd_flags_t flags,void * arg,size_t arg_len)165 log_add_range_arg(os_log_context_t ctx, os_log_fmt_cmd_type_t type, os_log_fmt_cmd_flags_t flags,
166     void *arg, size_t arg_len)
167 {
168 	struct os_log_fmt_range_s range;
169 
170 	if (!log_fits_cmd(ctx, sizeof(range))) {
171 		return ENOMEM;
172 	}
173 
174 	range = log_create_range(ctx, arg_len);
175 
176 	if (flags == OSLF_CMD_FLAG_PUBLIC) {
177 		if (ctx->ctx_pubdata_cnt == OS_LOG_MAX_PUB_ARGS) {
178 			return ENOMEM;
179 		}
180 		assert(ctx->ctx_pubdata_cnt < OS_LOG_MAX_PUB_ARGS);
181 		log_collect_public_range_data(ctx, &range, arg);
182 	}
183 	log_add_cmd(ctx, type, flags, &range, sizeof(range));
184 	ctx->ctx_hdr->hdr_cmd_cnt++;
185 
186 	return 0;
187 }
188 
189 /*
190  * Adds a scalar argument value to the main section.
191  */
192 static int
log_add_arg(os_log_context_t ctx,os_log_fmt_cmd_type_t type,void * arg,size_t arg_len)193 log_add_arg(os_log_context_t ctx, os_log_fmt_cmd_type_t type, void *arg, size_t arg_len)
194 {
195 	assert(type == OSLF_CMD_TYPE_COUNT || type == OSLF_CMD_TYPE_SCALAR);
196 	assert(arg_len < UINT16_MAX);
197 
198 	if (log_fits_cmd(ctx, arg_len)) {
199 		log_add_cmd(ctx, type, OSLF_CMD_FLAG_PUBLIC, arg, arg_len);
200 		ctx->ctx_hdr->hdr_cmd_cnt++;
201 		return 0;
202 	}
203 
204 	return ENOMEM;
205 }
206 
207 static void
log_encode_public_data(os_log_context_t ctx)208 log_encode_public_data(os_log_context_t ctx)
209 {
210 	const uint16_t orig_content_off = ctx->ctx_content_off;
211 	os_log_fmt_hdr_t const hdr = ctx->ctx_hdr;
212 	os_log_fmt_cmd_t cmd = (os_log_fmt_cmd_t)hdr->hdr_data;
213 
214 	assert(ctx->ctx_pubdata_cnt <= hdr->hdr_cmd_cnt);
215 
216 	for (int i = 0, pub_i = 0; i < hdr->hdr_cmd_cnt; i++, cmd = (os_log_fmt_cmd_t)(cmd->cmd_data + cmd->cmd_size)) {
217 		if (cmd->cmd_type != OSLF_CMD_TYPE_STRING) {
218 			continue;
219 		}
220 
221 		os_log_fmt_range_t const range __attribute__((aligned(8))) = (os_log_fmt_range_t)&cmd->cmd_data;
222 
223 		// Fix offset and length of the argument data in the hdr.
224 		log_range_update(range, ctx->ctx_content_off - orig_content_off,
225 		    MIN(range->length, ctx->ctx_content_sz - ctx->ctx_content_off));
226 
227 		if (range->truncated) {
228 			ctx->ctx_truncated = true;
229 		}
230 
231 		assert(pub_i < ctx->ctx_pubdata_cnt);
232 		log_add_range_data(ctx, range, ctx->ctx_pubdata[pub_i++]);
233 	}
234 }
235 
236 static bool
log_expand(os_log_context_t ctx,size_t new_size)237 log_expand(os_log_context_t ctx, size_t new_size)
238 {
239 	assert(new_size > ctx->ctx_buffer_sz);
240 
241 	if (!oslog_is_safe()) {
242 		return false;
243 	}
244 
245 	size_t final_size = new_size;
246 
247 	void *buf = logmem_alloc_locked(ctx->ctx_logmem, &final_size);
248 	if (!buf) {
249 		return false;
250 	}
251 	assert(final_size >= new_size);
252 
253 	// address length header + already stored data
254 	const size_t hdr_size = (uint8_t *)ctx->ctx_hdr - ctx->ctx_buffer;
255 	const size_t copy_size = hdr_size + sizeof(*ctx->ctx_hdr) + ctx->ctx_content_sz;
256 	assert(copy_size <= new_size);
257 	(void) memcpy(buf, ctx->ctx_buffer, copy_size);
258 
259 	if (ctx->ctx_allocated) {
260 		logmem_free_locked(ctx->ctx_logmem, ctx->ctx_buffer, ctx->ctx_buffer_sz);
261 	}
262 
263 	ctx->ctx_buffer = buf;
264 	ctx->ctx_buffer_sz = final_size;
265 	ctx->ctx_content_sz = (uint16_t)(ctx->ctx_buffer_sz - hdr_size - sizeof(*ctx->ctx_hdr));
266 	ctx->ctx_hdr = (os_log_fmt_hdr_t)&ctx->ctx_buffer[hdr_size];
267 	ctx->ctx_allocated = true;
268 
269 	return true;
270 }
271 
272 static int
log_encode_fmt_arg(void * arg,size_t arg_len,os_log_fmt_cmd_type_t type,os_log_context_t ctx)273 log_encode_fmt_arg(void *arg, size_t arg_len, os_log_fmt_cmd_type_t type, os_log_context_t ctx)
274 {
275 	int rc = 0;
276 
277 	switch (type) {
278 	case OSLF_CMD_TYPE_COUNT:
279 	case OSLF_CMD_TYPE_SCALAR:
280 		// Scrub kernel pointers.
281 		if (doprnt_hide_pointers && is_kernel_pointer(arg, arg_len)) {
282 			rc = log_add_range_arg(ctx, type, OSLF_CMD_FLAG_PRIVATE, NULL, 0);
283 			ctx->ctx_hdr->hdr_flags |= OSLF_HDR_FLAG_HAS_PRIVATE;
284 		} else {
285 			rc = log_add_arg(ctx, type, arg, arg_len);
286 		}
287 		break;
288 	case OSLF_CMD_TYPE_STRING:
289 		rc = log_add_range_arg(ctx, type, OSLF_CMD_FLAG_PUBLIC, arg, arg_len);
290 		ctx->ctx_hdr->hdr_flags |= OSLF_HDR_FLAG_HAS_NON_SCALAR;
291 		break;
292 	default:
293 		panic("Unsupported log value type");
294 	}
295 
296 	return rc;
297 }
298 
299 static int
log_encode_fmt(os_log_context_t ctx,const char * format,va_list args)300 log_encode_fmt(os_log_context_t ctx, const char *format, va_list args)
301 {
302 	const char *position = format;
303 
304 	while ((position = strchr(position, '%'))) {
305 		position++; // Look at character(s) after %.
306 
307 		int type = OST_INT;
308 		boolean_t has_precision = false;
309 		int precision = 0;
310 
311 		for (bool done = false; !done; position++) {
312 			union os_log_fmt_types_u value;
313 			size_t str_length;
314 			int err = 0;
315 
316 			switch (position[0]) {
317 			case '%':
318 				// %% prints % character
319 				done = true;
320 				break;
321 
322 			/* type of types or other */
323 			case 'l': // longer
324 				type++;
325 				break;
326 
327 			case 'h': // shorter
328 				type--;
329 				break;
330 
331 			case 'z':
332 				type = OST_SIZE;
333 				break;
334 
335 			case 'j':
336 				type = OST_INTMAX;
337 				break;
338 
339 			case 't':
340 				type = OST_PTRDIFF;
341 				break;
342 
343 			case 'q':
344 				type = OST_LONGLONG;
345 				break;
346 
347 			case '.': // precision
348 				if (position[1] == '*') {
349 					// Dynamic precision, argument holds actual value.
350 					precision = va_arg(args, int);
351 					position++;
352 				} else {
353 					// Static precision, the value follows in the fmt.
354 					precision = 0;
355 					while (is_digit(position[1])) {
356 						if (precision < LOG_FMT_MAX_PRECISION) {
357 							precision = 10 * precision + (position[1] - '0');
358 						}
359 						position++;
360 					}
361 					precision = MIN(precision, LOG_FMT_MAX_PRECISION);
362 				}
363 				err = log_encode_fmt_arg(&precision, sizeof(precision), OSLF_CMD_TYPE_COUNT, ctx);
364 				// A negative precision is treated as though it were missing.
365 				if (precision >= 0) {
366 					has_precision = true;
367 				}
368 				break;
369 
370 			case '-': // left-align
371 			case '+': // force sign
372 			case ' ': // prefix non-negative with space
373 			case '#': // alternate
374 			case '\'': // group by thousands
375 				break;
376 
377 			/* fixed types */
378 			case 'd': // integer
379 			case 'i': // integer
380 			case 'o': // octal
381 			case 'u': // unsigned
382 			case 'x': // hex
383 			case 'X': // upper-hex
384 				switch (type) {
385 				case OST_CHAR:
386 					value.ch = (char) va_arg(args, int);
387 					err = log_encode_fmt_arg(&value.ch, sizeof(value.ch), OSLF_CMD_TYPE_SCALAR, ctx);
388 					break;
389 
390 				case OST_SHORT:
391 					value.s = (short) va_arg(args, int);
392 					err = log_encode_fmt_arg(&value.s, sizeof(value.s), OSLF_CMD_TYPE_SCALAR, ctx);
393 					break;
394 
395 				case OST_INT:
396 					value.i = va_arg(args, int);
397 					err = log_encode_fmt_arg(&value.i, sizeof(value.i), OSLF_CMD_TYPE_SCALAR, ctx);
398 					break;
399 
400 				case OST_LONG:
401 					value.l = va_arg(args, long);
402 					err = log_encode_fmt_arg(&value.l, sizeof(value.l), OSLF_CMD_TYPE_SCALAR, ctx);
403 					break;
404 
405 				case OST_LONGLONG:
406 					value.ll = va_arg(args, long long);
407 					err = log_encode_fmt_arg(&value.ll, sizeof(value.ll), OSLF_CMD_TYPE_SCALAR, ctx);
408 					break;
409 
410 				case OST_SIZE:
411 					value.z = va_arg(args, size_t);
412 					err = log_encode_fmt_arg(&value.z, sizeof(value.z), OSLF_CMD_TYPE_SCALAR, ctx);
413 					break;
414 
415 				case OST_INTMAX:
416 					value.im = va_arg(args, intmax_t);
417 					err = log_encode_fmt_arg(&value.im, sizeof(value.im), OSLF_CMD_TYPE_SCALAR, ctx);
418 					break;
419 
420 				case OST_PTRDIFF:
421 					value.pd = va_arg(args, ptrdiff_t);
422 					err = log_encode_fmt_arg(&value.pd, sizeof(value.pd), OSLF_CMD_TYPE_SCALAR, ctx);
423 					break;
424 
425 				default:
426 					return EINVAL;
427 				}
428 				done = true;
429 				break;
430 
431 			case 'p': // pointer
432 				value.p = va_arg(args, void *);
433 				err = log_encode_fmt_arg(&value.p, sizeof(value.p), OSLF_CMD_TYPE_SCALAR, ctx);
434 				done = true;
435 				break;
436 
437 			case 'c': // char
438 				value.ch = (char) va_arg(args, int);
439 				err = log_encode_fmt_arg(&value.ch, sizeof(value.ch), OSLF_CMD_TYPE_SCALAR, ctx);
440 				done = true;
441 				break;
442 
443 			case 's': // string
444 				value.pch = va_arg(args, char *);
445 				if (!value.pch) {
446 					str_length = 0;
447 				} else if (has_precision) {
448 					assert(precision >= 0);
449 					str_length = strnlen(value.pch, precision);
450 				} else {
451 					str_length = strlen(value.pch) + 1;
452 				}
453 				err = log_encode_fmt_arg(value.pch, str_length, OSLF_CMD_TYPE_STRING, ctx);
454 				done = true;
455 				break;
456 
457 			case 'm':
458 				value.i = 0; // Does %m make sense in the kernel?
459 				err = log_encode_fmt_arg(&value.i, sizeof(value.i), OSLF_CMD_TYPE_SCALAR, ctx);
460 				done = true;
461 				break;
462 
463 			case '0' ... '9':
464 				// Skipping field width, libtrace takes care of it.
465 				break;
466 
467 			default:
468 				return EINVAL;
469 			}
470 
471 			if (slowpath(err)) {
472 				return err;
473 			}
474 		}
475 	}
476 
477 	return 0;
478 }
479 
480 OS_ALWAYS_INLINE
481 static inline void
tracepoint_buf_add(tracepoint_buf_t * tp,const void * data,size_t size)482 tracepoint_buf_add(tracepoint_buf_t *tp, const void *data, size_t size)
483 {
484 	assert((tp->tp_size + size) <= TRACEPOINT_BUF_MAX_SIZE);
485 	memcpy(&tp->tp_buf[tp->tp_size], data, size);
486 	tp->tp_size += size;
487 }
488 
489 static void
tracepoint_buf_location(tracepoint_buf_t * tpb,uintptr_t loc,size_t loc_size)490 tracepoint_buf_location(tracepoint_buf_t *tpb, uintptr_t loc, size_t loc_size)
491 {
492 	if (loc_size == sizeof(uintptr_t)) {
493 #if __LP64__
494 		loc_size = 6; // 48 bits are enough
495 #endif
496 		tracepoint_buf_add(tpb, (uintptr_t[]){ loc }, loc_size);
497 	} else {
498 		assert(loc_size == sizeof(uint32_t));
499 		tracepoint_buf_add(tpb, (uint32_t[]){ (uint32_t)loc }, loc_size);
500 	}
501 }
502 
503 static void
os_log_context_prepare_header(os_log_context_t ctx,size_t hdr_size)504 os_log_context_prepare_header(os_log_context_t ctx, size_t hdr_size)
505 {
506 	assert(hdr_size > 0 && hdr_size <= TRACEPOINT_BUF_MAX_SIZE);
507 	ctx->ctx_hdr = (os_log_fmt_hdr_t)&ctx->ctx_buffer[hdr_size];
508 	bzero(ctx->ctx_hdr, sizeof(*ctx->ctx_hdr));
509 	ctx->ctx_content_sz = (uint16_t)(ctx->ctx_buffer_sz - hdr_size - sizeof(*ctx->ctx_hdr));
510 }
511 
512 /*
513  * Encodes argument (meta)data into a format consumed by libtrace. Stores
514  * metadada for all arguments first. Metadata also include scalar argument
515  * values. Second step saves data which are encoded separately from respective
516  * metadata (like strings).
517  */
518 bool
os_log_context_encode(os_log_context_t ctx,const char * fmt,va_list args,uintptr_t loc,size_t loc_size,uint16_t subsystem_id)519 os_log_context_encode(os_log_context_t ctx, const char *fmt, va_list args,
520     uintptr_t loc, size_t loc_size, uint16_t subsystem_id)
521 {
522 	tracepoint_buf_t tpb = {
523 		.tp_buf     = ctx->ctx_buffer,
524 		.tp_size    = 0
525 	};
526 
527 	tracepoint_buf_location(&tpb, loc, loc_size);
528 	if (os_log_subsystem_id_valid(subsystem_id)) {
529 		tracepoint_buf_add(&tpb, &subsystem_id, sizeof(subsystem_id));
530 	}
531 	os_log_context_prepare_header(ctx, tpb.tp_size);
532 
533 	va_list args_copy;
534 	va_copy(args_copy, args);
535 
536 	int rc = log_encode_fmt(ctx, fmt, args);
537 
538 	va_end(args_copy);
539 
540 	switch (rc) {
541 	case EINVAL:
542 		// Bogus/Unsupported fmt string
543 		counter_inc(&oslog_p_fmt_invalid_msgcount);
544 		return false;
545 	case ENOMEM:
546 		/*
547 		 * The fmt contains unreasonable number of arguments (> 32) and
548 		 * we ran out of space. We could call log_expand()
549 		 * here and retry. However, using such formatting strings rather
550 		 * seem like a misuse of the logging system, hence error.
551 		 */
552 		counter_inc(&oslog_p_fmt_max_args_msgcount);
553 		return false;
554 	case 0:
555 		break;
556 	default:
557 		panic("unhandled return value");
558 	}
559 
560 	if (ctx->ctx_pubdata_sz == 0) {
561 		goto finish;
562 	}
563 
564 	/*
565 	 * Logmem may not have been set up yet when logging very early during
566 	 * the boot. Be sure to check its state.
567 	 */
568 	if (!log_fits(ctx, ctx->ctx_pubdata_sz) && logmem_ready(ctx->ctx_logmem)) {
569 		size_t space_needed = log_context_cursor(ctx) + ctx->ctx_pubdata_sz - ctx->ctx_buffer;
570 		space_needed = MIN(space_needed, logmem_max_size(ctx->ctx_logmem));
571 		(void) log_expand(ctx, space_needed);
572 	}
573 
574 	log_encode_public_data(ctx);
575 
576 	if (ctx->ctx_truncated) {
577 		counter_inc(&oslog_p_truncated_msgcount);
578 	}
579 finish:
580 	ctx->ctx_content_sz = (uint16_t)(log_context_cursor(ctx) - ctx->ctx_buffer);
581 	ctx->ctx_content_off = 0;
582 	return true;
583 }
584 
585 void
os_log_context_init(os_log_context_t ctx,logmem_t * logmem,uint8_t * buffer,size_t buffer_sz)586 os_log_context_init(os_log_context_t ctx, logmem_t *logmem, uint8_t *buffer, size_t buffer_sz)
587 {
588 	assert(logmem);
589 	assert(buffer);
590 	assert(buffer_sz > 0);
591 
592 	bzero(ctx, sizeof(*ctx));
593 	ctx->ctx_logmem = logmem;
594 	ctx->ctx_buffer = buffer;
595 	ctx->ctx_buffer_sz = buffer_sz;
596 }
597 
598 void
os_log_context_free(os_log_context_t ctx)599 os_log_context_free(os_log_context_t ctx)
600 {
601 	if (ctx->ctx_allocated) {
602 		logmem_free_locked(ctx->ctx_logmem, ctx->ctx_buffer, ctx->ctx_buffer_sz);
603 	}
604 }
605