symbols.c   [plain text]


/*
 * Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. The rights granted to you under the License
 * may not be used to create, or enable the creation or redistribution of,
 * unlawful or unlicensed copies of an Apple operating system, or to
 * circumvent, violate, or enable the circumvention or violation of, any
 * terms of an Apple operating system software license agreement.
 * 
 * Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
 */

/*-
 * Copyright (c) 2004 Networks Associates Technology, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <mach/mach_types.h>
#include <mach-o/mach_header.h>
#include <mach-o/nlist.h>
#include <mach/kmod.h>
#include <libsa/stdlib.h>
#include <kern/misc_protos.h>

static const struct nlist *
syms_find(const struct nlist *syms, int nsyms, vm_offset_t addr,
    vm_offset_t *ofs)
{
	const struct nlist *best = 0;
	int                 i;

	for (i = 0; i < nsyms; i++) {
		int st = syms[i].n_type & N_TYPE;

		if (st == N_SECT || st == N_ABS) {
			if (syms[i].n_value == addr) {
				*ofs = 0;
				return (syms+i);
			}
			else if (syms[i].n_value < addr &&
				 (best == 0 ||
				  (syms[i].n_value > best->n_value))) {
				*ofs = addr - syms[i].n_value;
				best = syms+i;
			}
		}
	}

	return (best);
}

static const char *
syms_getname(const struct symtab_command *sc, const char *ss,
	     const struct nlist *sp)
{
	if (sp->n_un.n_strx == 0)
		return ("");
	else if ((unsigned)sp->n_un.n_strx > sc->strsize)
		return ("*bad string*");
	else
		return (ss + sp->n_un.n_strx);
}

/* Search for a symbol in the given object file, which must either
 * have a LINKEDIT segment or have been read directly into memory
 * and isload passed as 1.

 * If mh has a LINKEDIT segment, an object file loaded in the normal
 * way will have the symbol table available at the load address.
 */

static const char *
syms_nameforaddr1(const struct mach_header *mh, int isload,
    vm_offset_t addr, vm_offset_t *ofs)
{
	const struct symtab_command  *sc = NULL;
	const struct segment_command *le = NULL;
	const struct segment_command *p;
	const struct segment_command *sym = NULL;
	const struct nlist           *syms;
	const char                   *strings;
	unsigned int                  i;

	p = (const struct segment_command *) (&mh[1]);

	for (i = 0; i < mh->ncmds; i++) {
		if (p->cmd == LC_SYMTAB)
			sc = (const struct symtab_command *) p;
		else if (p->cmd == LC_SEGMENT &&
		    !strncmp(p->segname, "__LINKEDIT", sizeof(p->segname)))
			le = p;

		/* only try to find a name for an address that came from
		 * a text section.
		 */
		if (p->cmd == LC_SEGMENT &&
		    addr >= p->vmaddr && addr < p->vmaddr + p->vmsize) {
			unsigned int j;

			const struct section *sp = (const struct section *)
			    (((const char *) p) + sizeof(struct segment_command));

			for (j = 0; j < p->nsects; j++) {
				if (addr >= sp[j].addr &&
				    addr <  sp[j].addr + sp[j].size &&
				    !strncmp (sp[j].sectname, "__text",
					    	sizeof(sp[j].sectname))) {
					sym = p;
					break;
				}
			}
		}
		p = (const struct segment_command *)
			(((const char *) p) + p->cmdsize);
	}

	if (sc == 0 || sym == NULL)
		return (NULL);

	if (!isload) {
		syms = (const struct nlist *) (((const char *) mh) + sc->symoff);
		strings = ((const char *) mh) + sc->stroff;
	}
	else if (le) {
		syms = (const struct nlist *) le->vmaddr;
		strings = (const char *)
			(le->vmaddr + sc->nsyms * sizeof(struct nlist));
	} else
		return (NULL);

	const struct nlist *sp = syms_find(syms, sc->nsyms, addr, ofs);
	if (sp)
		return syms_getname(sc, strings, sp);

	return (NULL);
}

extern struct mach_header  _mh_execute_header;
extern kmod_info_t        *kmod;

/* Search for a symbol and return the name, offset, and module in which the
 * address was found. A null module means the kernel itself.
 */

const char *
syms_nameforaddr(vm_offset_t addr, vm_offset_t *ofs, kmod_info_t **km)
{
	const char  *name = NULL;

	name = syms_nameforaddr1(&_mh_execute_header, 1, addr, ofs);
	if (name) {
		*km = NULL;
		return (name);
	}

	return (NULL);
}


/* Format the results of calling syms_nameforaddr into a single string.
 * The buffer must be at least 13 bytes long; 80 is recommended.
 */

int
syms_formataddr(vm_offset_t addr, char *out, vm_offset_t outsize)
{
	vm_offset_t  ofs;
	kmod_info_t *k = NULL;
	const char  *name;

	name = syms_nameforaddr(addr, &ofs, &k);

	if (ofs > 0x100000)
		name = NULL;

	if (name != NULL) {
		if (k != NULL)
			snprintf(out, outsize, "0x%08X <%s:%s + %d>", addr, 
			    k->name, name, ofs);
		else
			snprintf(out, outsize, "0x%08X <%s + %d>", addr, name,
			    ofs);

		return (1);
	}
	else {
		snprintf(out, outsize, "0x%08X", addr);
		return (0);
	}
}