// Copyright 2009 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package gosym implements access to the Go symbol
// and line number tables embedded in Go binaries generated
// by the gc compilers.
package gosym

// The table format is a variant of the format used in Plan 9's a.out
// format, documented at http://plan9.bell-labs.com/magic/man2html/6/a.out.
// The best reference for the differences between the Plan 9 format
// and the Go format is the runtime source, specifically ../../runtime/symtab.c.

import (
	"encoding/binary";
	"fmt";
	"os";
	"strconv";
	"strings";
)

/*
 * Symbols
 */

// A Sym represents a single symbol table entry.
type Sym struct {
	Value	uint64;
	Type	byte;
	Name	string;
	GoType	uint64;
	// If this symbol if a function symbol, the corresponding Func
	Func	*Func;
}

// Static returns whether this symbol is static (not visible outside its file).
func (s *Sym) Static() bool {
	return s.Type >= 'a';
}

// PackageName returns the package part of the symbol name,
// or the empty string if there is none.
func (s *Sym) PackageName() string {
	if i := strings.Index(s.Name, "."); i != -1 {
		return s.Name[0:i];
	}
	return "";
}

// ReceiverName returns the receiver type name of this symbol,
// or the empty string if there is none.
func (s *Sym) ReceiverName() string {
	l := strings.Index(s.Name, ".");
	r := strings.LastIndex(s.Name, ".");
	if l == -1 || r == -1 || l == r {
		return "";
	}
	return s.Name[l+1 : r];
}

// BaseName returns the symbol name without the package or receiver name.
func (s *Sym) BaseName() string {
	if i := strings.LastIndex(s.Name, "."); i != -1 {
		return s.Name[i+1 : len(s.Name)];
	}
	return s.Name;
}

// A Func collects information about a single function.
type Func struct {
	Entry	uint64;
	*Sym;
	End		uint64;
	Params		[]*Sym;
	Locals		[]*Sym;
	FrameSize	int;
	LineTable	*LineTable;
	Obj		*Obj;
}

// An Obj represents a single object file.
type Obj struct {
	Funcs	[]Func;
	Paths	[]Sym;
}

/*
 * Symbol tables
 */

// Table represents a Go symbol table.  It stores all of the
// symbols decoded from the program and provides methods to translate
// between symbols, names, and addresses.
type Table struct {
	Syms	[]Sym;
	Funcs	[]Func;
	Files	map[string]*Obj;
	Objs	[]Obj;
	//	textEnd uint64;
}

type sym struct {
	value	uint32;
	gotype	uint32;
	typ	byte;
	name	[]byte;
}

func walksymtab(data []byte, fn func(sym) os.Error) os.Error {
	var s sym;
	p := data;
	for len(p) >= 6 {
		s.value = binary.BigEndian.Uint32(p[0:4]);
		typ := p[4];
		if typ&0x80 == 0 {
			return &DecodingError{len(data)-len(p)+4, "bad symbol type", typ};
		}
		typ &^= 0x80;
		s.typ = typ;
		p = p[5:len(p)];
		var i int;
		var nnul int;
		for i = 0; i < len(p); i++ {
			if p[i] == 0 {
				nnul = 1;
				break;
			}
		}
		switch typ {
		case 'z', 'Z':
			p = p[i+nnul : len(p)];
			for i = 0; i+2 <= len(p); i += 2 {
				if p[i] == 0 && p[i+1] == 0 {
					nnul = 2;
					break;
				}
			}
		}
		if i+nnul+4 > len(p) {
			return &DecodingError{len(data), "unexpected EOF", nil};
		}
		s.name = p[0:i];
		i += nnul;
		s.gotype = binary.BigEndian.Uint32(p[i : i+4]);
		p = p[i+4 : len(p)];
		fn(s);
	}
	return nil;
}

// NewTable decodes the Go symbol table in data,
// returning an in-memory representation.
func NewTable(symtab []byte, pcln *LineTable) (*Table, os.Error) {
	var n int;
	err := walksymtab(symtab, func(s sym) os.Error {
		n++;
		return nil;
	});
	if err != nil {
		return nil, err;
	}

	var t Table;
	fname := make(map[uint16]string);
	t.Syms = make([]Sym, 0, n);
	nf := 0;
	nz := 0;
	lasttyp := uint8(0);
	err = walksymtab(symtab, func(s sym) os.Error {
		n := len(t.Syms);
		t.Syms = t.Syms[0 : n+1];
		ts := &t.Syms[n];
		ts.Type = s.typ;
		ts.Value = uint64(s.value);
		ts.GoType = uint64(s.gotype);
		switch s.typ {
		default:
			// rewrite name to use . instead of · (c2 b7)
			w := 0;
			b := s.name;
			for i := 0; i < len(b); i++ {
				if b[i] == 0xc2 && i+1 < len(b) && b[i+1] == 0xb7 {
					i++;
					b[i] = '.';
				}
				b[w] = b[i];
				w++;
			}
			ts.Name = string(s.name[0:w]);
		case 'z', 'Z':
			if lasttyp != 'z' && lasttyp != 'Z' {
				nz++;
			}
			for i := 0; i < len(s.name); i += 2 {
				eltIdx := binary.BigEndian.Uint16(s.name[i : i+2]);
				elt, ok := fname[eltIdx];
				if !ok {
					return &DecodingError{-1, "bad filename code", eltIdx};
				}
				if n := len(ts.Name); n > 0 && ts.Name[n-1] != '/' {
					ts.Name += "/";
				}
				ts.Name += elt;
			}
		}
		switch s.typ {
		case 'T', 't', 'L', 'l':
			nf++;
		case 'f':
			fname[uint16(s.value)] = ts.Name;
		}
		lasttyp = s.typ;
		return nil;
	});
	if err != nil {
		return nil, err;
	}

	t.Funcs = make([]Func, 0, nf);
	t.Objs = make([]Obj, 0, nz);
	t.Files = make(map[string]*Obj);

	// Count text symbols and attach frame sizes, parameters, and
	// locals to them.  Also, find object file boundaries.
	var obj *Obj;
	lastf := 0;
	for i := 0; i < len(t.Syms); i++ {
		sym := &t.Syms[i];
		switch sym.Type {
		case 'Z', 'z':	// path symbol
			// Finish the current object
			if obj != nil {
				obj.Funcs = t.Funcs[lastf:len(t.Funcs)];
			}
			lastf = len(t.Funcs);

			// Start new object
			n := len(t.Objs);
			t.Objs = t.Objs[0 : n+1];
			obj = &t.Objs[n];

			// Count & copy path symbols
			var end int;
			for end = i+1; end < len(t.Syms); end++ {
				if c := t.Syms[end].Type; c != 'Z' && c != 'z' {
					break;
				}
			}
			obj.Paths = t.Syms[i:end];
			i = end-1;	// loop will i++

			// Record file names
			depth := 0;
			for j := range obj.Paths {
				s := &obj.Paths[j];
				if s.Name == "" {
					depth--;
				} else {
					if depth == 0 {
						t.Files[s.Name] = obj;
					}
					depth++;
				}
			}

		case 'T', 't', 'L', 'l':	// text symbol
			if n := len(t.Funcs); n > 0 {
				t.Funcs[n-1].End = sym.Value;
			}
			if sym.Name == "etext" {
				continue;
			}

			// Count parameter and local (auto) syms
			var np, na int;
			var end int;
		countloop:
			for end = i+1; end < len(t.Syms); end++ {
				switch t.Syms[end].Type {
				case 'T', 't', 'L', 'l', 'Z', 'z':
					break countloop;
				case 'p':
					np++;
				case 'a':
					na++;
				}
			}

			// Fill in the function symbol
			n := len(t.Funcs);
			t.Funcs = t.Funcs[0 : n+1];
			fn := &t.Funcs[n];
			sym.Func = fn;
			fn.Params = make([]*Sym, 0, np);
			fn.Locals = make([]*Sym, 0, na);
			fn.Sym = sym;
			fn.Entry = sym.Value;
			fn.Obj = obj;
			if pcln != nil {
				fn.LineTable = pcln.slice(fn.Entry);
				pcln = fn.LineTable;
			}
			for j := i; j < end; j++ {
				s := &t.Syms[j];
				switch s.Type {
				case 'm':
					fn.FrameSize = int(s.Value);
				case 'p':
					n := len(fn.Params);
					fn.Params = fn.Params[0 : n+1];
					fn.Params[n] = s;
				case 'a':
					n := len(fn.Locals);
					fn.Locals = fn.Locals[0 : n+1];
					fn.Locals[n] = s;
				}
			}
			i = end-1;	// loop will i++
		}
	}
	if obj != nil {
		obj.Funcs = t.Funcs[lastf:len(t.Funcs)];
	}
	return &t, nil;
}

// PCToFunc returns the function containing the program counter pc,
// or nil if there is no such function.
func (t *Table) PCToFunc(pc uint64) *Func {
	funcs := t.Funcs;
	for len(funcs) > 0 {
		m := len(funcs)/2;
		fn := &funcs[m];
		switch {
		case pc < fn.Entry:
			funcs = funcs[0:m];
		case fn.Entry <= pc && pc < fn.End:
			return fn;
		default:
			funcs = funcs[m+1 : len(funcs)];
		}
	}
	return nil;
}

// PCToLine looks up line number information for a program counter.
// If there is no information, it returns fn == nil.
func (t *Table) PCToLine(pc uint64) (file string, line int, fn *Func) {
	if fn = t.PCToFunc(pc); fn == nil {
		return;
	}
	file, line = fn.Obj.lineFromAline(fn.LineTable.PCToLine(pc));
	return;
}

// LineToPC looks up the first program counter on the given line in
// the named file.  Returns UnknownPathError or UnknownLineError if
// there is an error looking up this line.
func (t *Table) LineToPC(file string, line int) (pc uint64, fn *Func, err os.Error) {
	obj, ok := t.Files[file];
	if !ok {
		return 0, nil, UnknownFileError(file);
	}
	abs, err := obj.alineFromLine(file, line);
	if err != nil {
		return;
	}
	for i := range obj.Funcs {
		f := &obj.Funcs[i];
		pc := f.LineTable.LineToPC(abs, f.End);
		if pc != 0 {
			return pc, f, nil;
		}
	}
	return 0, nil, &UnknownLineError{file, line};
}

// LookupSym returns the text, data, or bss symbol with the given name,
// or nil if no such symbol is found.
func (t *Table) LookupSym(name string) *Sym {
	// TODO(austin) Maybe make a map
	for i := range t.Syms {
		s := &t.Syms[i];
		switch s.Type {
		case 'T', 't', 'L', 'l', 'D', 'd', 'B', 'b':
			if s.Name == name {
				return s;
			}
		}
	}
	return nil;
}

// LookupFunc returns the text, data, or bss symbol with the given name,
// or nil if no such symbol is found.
func (t *Table) LookupFunc(name string) *Func {
	for i := range t.Funcs {
		f := &t.Funcs[i];
		if f.Sym.Name == name {
			return f;
		}
	}
	return nil;
}

// SymByAddr returns the text, data, or bss symbol starting at the given address.
// TODO(rsc): Allow lookup by any address within the symbol.
func (t *Table) SymByAddr(addr uint64) *Sym {
	// TODO(austin) Maybe make a map
	for i := range t.Syms {
		s := &t.Syms[i];
		switch s.Type {
		case 'T', 't', 'L', 'l', 'D', 'd', 'B', 'b':
			if s.Value == addr {
				return s;
			}
		}
	}
	return nil;
}

/*
 * Object files
 */

func (o *Obj) lineFromAline(aline int) (string, int) {
	type stackEnt struct {
		path	string;
		start	int;
		offset	int;
		prev	*stackEnt;
	}

	noPath := &stackEnt{"", 0, 0, nil};
	tos := noPath;

	// TODO(austin) I have no idea how 'Z' symbols work, except
	// that they pop the stack.
pathloop:
	for _, s := range o.Paths {
		val := int(s.Value);
		switch {
		case val > aline:
			break pathloop;

		case val == 1:
			// Start a new stack
			tos = &stackEnt{s.Name, val, 0, noPath};

		case s.Name == "":
			// Pop
			if tos == noPath {
				return "<malformed symbol table>", 0;
			}
			tos.prev.offset += val - tos.start;
			tos = tos.prev;

		default:
			// Push
			tos = &stackEnt{s.Name, val, 0, tos};
		}
	}

	if tos == noPath {
		return "", 0;
	}
	return tos.path, aline - tos.start - tos.offset + 1;
}

func (o *Obj) alineFromLine(path string, line int) (int, os.Error) {
	if line < 1 {
		return 0, &UnknownLineError{path, line};
	}

	for i, s := range o.Paths {
		// Find this path
		if s.Name != path {
			continue;
		}

		// Find this line at this stack level
		depth := 0;
		var incstart int;
		line += int(s.Value);
	pathloop:
		for _, s := range o.Paths[i:len(o.Paths)] {
			val := int(s.Value);
			switch {
			case depth == 1 && val >= line:
				return line-1, nil;

			case s.Name == "":
				depth--;
				if depth == 0 {
					break pathloop;
				} else if depth == 1 {
					line += val-incstart;
				}

			default:
				if depth == 1 {
					incstart = val;
				}
				depth++;
			}
		}
		return 0, &UnknownLineError{path, line};
	}
	return 0, UnknownFileError(path);
}

/*
 * Errors
 */

// UnknownFileError represents a failure to find the specific file in
// the symbol table.
type UnknownFileError string

func (e UnknownFileError) String() string {
	return "unknown file: " + string(e);
}

// UnknownLineError represents a failure to map a line to a program
// counter, either because the line is beyond the bounds of the file
// or because there is no code on the given line.
type UnknownLineError struct {
	File	string;
	Line	int;
}

func (e *UnknownLineError) String() string {
	return "no code at " + e.File + ":" + strconv.Itoa(e.Line);
}

// DecodingError represents an error during the decoding of
// the symbol table.
type DecodingError struct {
	off	int;
	msg	string;
	val	interface{};
}

func (e *DecodingError) String() string {
	msg := e.msg;
	if e.val != nil {
		msg += fmt.Sprintf(" '%v'", e.val);
	}
	msg += fmt.Sprintf(" at byte %#x", e.off);
	return msg;
}
