/*
* Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.max.tele.debug.darwin;
import java.io.*;
import java.nio.*;
/**
* (Limited) Access to Mach_O 64-bit format files. See /usr/include/mach-o/*.h for source.
* Note that a file may (unusually) contain multiple binaries for different architectures,
* see /usr/include/mach-o/fat.h. Such a file is called a universal binary file, (cf an archive file).
*
*/
public class DarwinMachO {
/**
* Encapsulates whether the MachO file is embedded in a universal binary file.
*/
public static class MachORandomAccessFile extends RandomAccessFile {
/**
* If non-zero, offset to start of architecture-specific MachO file in FAT file.
*/
private int fatOffset;
private MachORandomAccessFile(String path) throws FileNotFoundException {
super(path, "r");
}
@Override
public void seek(long filePos) throws IOException {
super.seek(filePos + fatOffset);
}
@Override
public long getFilePointer() throws IOException {
long fp = super.getFilePointer();
return fp - fatOffset;
}
}
public final MachORandomAccessFile raf;
private Header header;
private LoadCommand[] loadCommands;
public DarwinMachO(String path) throws FileNotFoundException, IOException {
raf = new MachORandomAccessFile(path);
header = new Header();
}
private class FatArch {
final static int X86_64 = 0x01000007;
int cputype;
int cpusubtype;
int offset;
int size;
int align;
private FatArch() throws IOException {
cputype = raf.readInt();
cpusubtype = raf.readInt();
offset = raf.readInt();
size = raf.readInt();
align = raf.readInt();
}
}
public class Header {
private static final int FAT_MAGIC = 0xcafebabe;
public final int magic;
public final int cputype;
public final int cpusubtype;
public final int filetype;
public final int ncmds;
public final int sizeofcmds;
public final int flags;
public final int reserved;
private Header() throws IOException {
int magic = raf.readInt();
boolean found = false;
if (magic == FAT_MAGIC) {
// find our architecture subfile
int nFatArch = raf.readInt();
for (int i = 0; i < nFatArch; i++) {
FatArch fatArch = new FatArch();
if (fatArch.cputype == FatArch.X86_64) {
found = true;
raf.seek(fatArch.offset);
raf.fatOffset = fatArch.offset;
magic = readInt();
break;
}
}
assert found : "failed to find X86_64 architecture in MachO FAT file";
}
this.magic = magic;
cputype = readInt();
cpusubtype = readInt();
filetype = readInt();
ncmds = readInt();
sizeofcmds = readInt();
flags = readInt();
reserved = readInt();
}
}
public Header getHeader() throws IOException {
if (header == null) {
header = new Header();
}
return header;
}
/**
* Common base class for all Mach-O load command types.
*/
public class LoadCommand {
public static final int LC_SEGMENT_64 = 0x19;
public static final int LC_THREAD = 0x4;
public static final int LC_SYMTAB = 0x2;
public final int cmd;
public final int cmdsize;
protected LoadCommand() throws IOException {
this.cmd = readInt();
this.cmdsize = readInt();
}
public String typeName() {
switch (cmd) {
case LC_SEGMENT_64:
return "LC_SEGMENT_64";
case LC_THREAD:
return "LC_THREAD";
case LC_SYMTAB:
return "LC_SYMTAB";
default:
return "LC #" + cmd;
}
}
}
/**
* Reads a load command structure starting at the current file position, invoking
* the appropriate subclass {@code read} command, based on the {@code cmd} field.
* Leaves the file pointer at the next load command (if any).
*
* @return instance of the appropriate subclass for discovered command type
* @throws IOException
*/
private LoadCommand readNextLoadCommand() throws IOException {
LoadCommand result = null;
final long ptr = raf.getFilePointer();
final int cmd = readInt();
final int cmdsize = readInt();
raf.seek(ptr);
switch (cmd) {
case LoadCommand.LC_SEGMENT_64:
result = new Segment64LoadCommand();
break;
case LoadCommand.LC_THREAD:
result = new ThreadLoadCommand();
break;
case LoadCommand.LC_SYMTAB:
result = new SymTabLoadCommand();
break;
default:
result = new LoadCommand();
}
// skip over entire command
raf.seek(ptr + cmdsize);
return result;
}
public LoadCommand[] getLoadCommands() throws IOException {
if (loadCommands == null) {
getHeader();
loadCommands = new LoadCommand[header.ncmds];
for (int i = 0; i < header.ncmds; i++) {
loadCommands[i] = readNextLoadCommand();
}
}
return loadCommands;
}
public final class Segment64LoadCommand extends LoadCommand {
public final String segName;
public final long vmaddr;
public final long vmsize;
public final long fileoff;
public final long filesize;
public final int maxprot;
public final int initprot;
public final int nsects;
public final int flags;
public final Section64[] sections;
private Segment64LoadCommand() throws IOException {
final byte[] segname = new byte[16];
for (int i = 0; i < 16; i++) {
segname[i] = raf.readByte();
}
segName = new String(segname);
vmaddr = readLong();
vmsize = readLong();
fileoff = readLong();
filesize = readLong();
maxprot = readInt();
initprot = readInt();
nsects = readInt();
flags = readInt();
sections = new Section64[nsects];
for (int i = 0; i < nsects; i++) {
sections[i] = new Section64(this);
}
}
}
public class Section64 {
public final String sectname;
public final String segname;
public final long addr;
public final long size;
public final int offset;
public final int align;
public final int reloff;
public final int nreloc;
public final int flags;
public final int reserved1;
public final int reserved2;
public final int reserved3;
public final Segment64LoadCommand owningSegment;
private Section64(Segment64LoadCommand segment64) throws IOException {
owningSegment = segment64;
sectname = readName();
segname = readName();
addr = readLong();
size = readLong();
offset = readInt();
align = readInt();
reloff = readInt();
nreloc = readInt();
flags = readInt();
reserved1 = readInt();
reserved2 = readInt();
reserved3 = readInt();
}
private String readName() throws IOException {
byte[] nameBytes = new byte[16];
int length = 0;
for (int i = 0; i < nameBytes.length; i++) {
nameBytes[i] = raf.readByte();
if (nameBytes[i] != 0) {
length++;
}
}
return new String(nameBytes, 0, length);
}
public boolean isText() {
return segname.equals("__TEXT");
}
}
public class ThreadLoadCommand extends LoadCommand {
public final ThreadRegState regstate;
public final ThreadFPRegState fpregstate;
public final ThreadExceptionState exstate;
ThreadLoadCommand() throws IOException {
regstate = new ThreadRegState();
fpregstate = new ThreadFPRegState();
exstate = new ThreadExceptionState();
}
}
public class ThreadState {
public final int flavor;
public final int count;
protected ThreadState() throws IOException {
flavor = readInt();
count = readInt();
}
}
public class ThreadRegState extends ThreadState {
public final int tsh_flavor;
public final int tsh_count;
public final ByteBuffer regbytes; // we also store registers as a directly allocated ByteBuffer for passing to native code
public final long rax;
public final long rbx;
public final long rcx;
public final long rdx;
public final long rdi;
public final long rsi;
public final long rbp;
public final long rsp;
public final long r8;
public final long r9;
public final long r10;
public final long r11;
public final long r12;
public final long r13;
public final long r14;
public final long r15;
public final long rip;
public final long rflags;
public final long cs;
public final long fs;
public final long gs;
ThreadRegState() throws IOException {
tsh_flavor = readInt();
tsh_count = readInt();
final long ptr = raf.getFilePointer();
rax = readLong();
rbx = readLong();
rcx = readLong();
rdx = readLong();
rdi = readLong();
rsi = readLong();
rbp = readLong();
rsp = readLong();
r8 = readLong();
r9 = readLong();
r10 = readLong();
r11 = readLong();
r12 = readLong();
r13 = readLong();
r14 = readLong();
r15 = readLong();
rip = readLong();
rflags = readLong();
cs = readLong();
fs = readLong();
gs = readLong();
raf.seek(ptr);
// read and store again as byte array
regbytes = ByteBuffer.allocateDirect(tsh_count * 4);
for (int i = 0; i < tsh_count * 4; i++) {
regbytes.put(raf.readByte());
}
}
}
public class ThreadFPRegState extends ThreadState {
public final int fsh_flavor;
public final int fsh_count;
ByteBuffer regbytes; // way too complex for individual declarations; do it all via native code
ThreadFPRegState() throws IOException {
fsh_flavor = readInt();
fsh_count = readInt();
regbytes = ByteBuffer.allocateDirect(fsh_count * 4);
for (int i = 0; i < fsh_count * 4; i++) {
regbytes.put(raf.readByte());
}
}
}
public class ThreadExceptionState extends ThreadState {
public final int esh_flavor;
public final int esh_count;
public final int trapno;
public final int err;
public final long faultvaddr;
ThreadExceptionState() throws IOException {
super();
esh_flavor = readInt();
esh_count = readInt();
trapno = readInt();
err = readInt();
faultvaddr = readLong();
}
}
public class SymTabLoadCommand extends LoadCommand {
public final int symoff;
public final int nsyms;
public final int stroff;
public final int strsize;
/**
* Lazily created string table.
*/
private byte[] stringTable;
/**
* Lazily created symbol table.
*/
private NList64[] symbolTable;
SymTabLoadCommand() throws IOException {
super();
symoff = readInt();
nsyms = readInt();
stroff = readInt();
strsize = readInt();
}
public NList64[] getSymbolTable() throws IOException {
if (symbolTable != null) {
return symbolTable;
}
stringTable = new byte[strsize];
raf.seek(stroff);
for (int i = 0; i < strsize; i++) {
stringTable[i] = raf.readByte();
}
symbolTable = new NList64[nsyms];
raf.seek(symoff);
for (int i = 0; i < nsyms; i++) {
symbolTable[i] = new NList64();
}
return symbolTable;
}
public String getSymbolName(NList64 nlist64) {
String symbol = "";
if (nlist64.strx != 0) {
byte sb = stringTable[nlist64.strx];
int sl = 0;
while (sb != 0) {
sb = stringTable[nlist64.strx + sl];
sl++;
}
// remove leading/trailing underscores which bracket all symbols
symbol = new String(stringTable, nlist64.strx + 1, sl - 2);
}
return symbol;
}
}
public class NList64 {
public static final int STAB = 0xe0;
public static final int PEXT = 0x10;
public static final int TYPE = 0xe;
public static final int EXT = 0x1;
public static final int UNDF = 0x0;
public static final int ABS = 0x2;
public static final int SECT = 0xe;
public static final int PBUD = 0xc;
public static final int INDR = 0xa;
public final int strx;
public final byte type;
public final byte sect;
public final short desc;
public final long value;
NList64() throws IOException {
strx = readInt();
type = raf.readByte();
sect = raf.readByte();
desc = readShort();
value = readLong();
}
}
/**
* Locates a given section within a given array of load commands.
* Sections are numbered from 1 as they occur within SEGMENT_64 commands.
* @param loadCommands
* @param sectToFind
*/
public static Section64 getSection(LoadCommand[] loadCommands, int sectToFind) {
int sect = 1;
for (int i = 0; i < loadCommands.length; i++) {
if (loadCommands[i].cmd == LoadCommand.LC_SEGMENT_64) {
Segment64LoadCommand slc = (Segment64LoadCommand) loadCommands[i];
if (sectToFind < sect + slc.nsects) {
return slc.sections[sectToFind - sect];
}
sect += slc.nsects;
}
}
return null;
}
public short readShort() throws IOException {
final int b1 = raf.read();
final int b2 = raf.read();
return (short) (((b2 << 8) | b1) & 0xFFFF);
}
public int readInt() throws IOException {
final int b1 = raf.read();
final int b2 = raf.read();
final int b3 = raf.read();
final int b4 = raf.read();
return (b4 << 24) | (b3 << 16) | (b2 << 8) | b1;
}
public long readLong() throws IOException {
final long lw = readInt();
final long hw = readInt();
return hw << 32 | (lw & 0xFFFFFFFFL);
}
}